mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 07:34:27 +02:00
Basic states
This commit is contained in:
parent
c1bbe7627d
commit
b304db6a2f
@ -256,7 +256,7 @@ impl Application for AppState {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn destroy(&mut self) {
|
fn destructor(&mut self) {
|
||||||
// TODO: Eventually move some thread logic into the app creation, and destroy here?
|
// TODO: Eventually move some thread logic into the app creation, and destroy here?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ pub trait Application: Sized {
|
|||||||
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<'static, Self::Message>;
|
fn view<'b>(&mut self, ctx: &mut ViewContext<'_>) -> Element<'static, Self::Message>;
|
||||||
|
|
||||||
/// To run upon stopping the application.
|
/// To run upon stopping the application.
|
||||||
fn destroy(&mut self) {}
|
fn destructor(&mut self) {}
|
||||||
|
|
||||||
/// An optional event handler, intended for use with global shortcuts or events.
|
/// An optional event handler, intended for use with global shortcuts or events.
|
||||||
/// This will be run *after* trying to send the events into the user interface, and
|
/// This will be run *after* trying to send the events into the user interface, and
|
||||||
|
@ -5,23 +5,28 @@ pub mod widget;
|
|||||||
pub use widget::*;
|
pub use widget::*;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use tui::{layout::Rect, Frame};
|
use tui::Frame;
|
||||||
|
|
||||||
use super::{Bounds, DrawContext, Event, LayoutNode, Size, Status};
|
use super::{Bounds, DrawContext, Event, LayoutNode, Size, StateContext, Status};
|
||||||
|
|
||||||
/// A component displays information and can be interacted with.
|
/// A component displays information and can be interacted with.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait TmpComponent<Message> {
|
pub trait TmpComponent<Message> {
|
||||||
/// Draws the component.
|
/// Draws the component.
|
||||||
fn draw<Backend>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, Backend>)
|
fn draw<Backend>(
|
||||||
where
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>,
|
||||||
|
frame: &mut Frame<'_, Backend>,
|
||||||
|
) where
|
||||||
Backend: tui::backend::Backend;
|
Backend: tui::backend::Backend;
|
||||||
|
|
||||||
/// How a component should react to an [`Event`].
|
/// How a component should react to an [`Event`].
|
||||||
///
|
///
|
||||||
/// Defaults to just ignoring the event.
|
/// Defaults to just ignoring the event.
|
||||||
fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>, event: Event,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
Status::Ignored
|
Status::Ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
use tui::{backend::Backend, layout::Rect, Frame};
|
use tui::{backend::Backend, Frame};
|
||||||
|
|
||||||
use crate::tuine::{DrawContext, Event, Status, TmpComponent};
|
use crate::tuine::{DrawContext, Event, StateContext, Status, TmpComponent};
|
||||||
|
|
||||||
pub struct Block {}
|
pub struct Block {}
|
||||||
|
|
||||||
impl<Message> TmpComponent<Message> for Block {
|
impl<Message> TmpComponent<Message> for Block {
|
||||||
fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>)
|
fn draw<B>(
|
||||||
where
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>,
|
||||||
|
_frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>, _event: Event,
|
||||||
|
_messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
Status::Ignored
|
Status::Ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
use tui::{backend::Backend, layout::Rect, Frame};
|
use tui::{backend::Backend, Frame};
|
||||||
|
|
||||||
use crate::tuine::{DrawContext, Event, Status, TmpComponent};
|
use crate::tuine::{DrawContext, Event, StateContext, Status, TmpComponent};
|
||||||
|
|
||||||
pub struct Carousel {}
|
pub struct Carousel {}
|
||||||
|
|
||||||
impl<Message> TmpComponent<Message> for Carousel {
|
impl<Message> TmpComponent<Message> for Carousel {
|
||||||
fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>)
|
fn draw<B>(
|
||||||
where
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>,
|
||||||
|
_frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>, _event: Event,
|
||||||
|
_messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
Status::Ignored
|
Status::Ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use tui::{backend::Backend, layout::Rect, Frame};
|
use tui::{backend::Backend, Frame};
|
||||||
|
|
||||||
use crate::tuine::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent};
|
use crate::tuine::{
|
||||||
|
Bounds, DrawContext, Element, Event, LayoutNode, Size, StateContext, Status, TmpComponent,
|
||||||
|
};
|
||||||
|
|
||||||
/// A [`Container`] just contains a child, as well as being able to be sized.
|
/// A [`Container`] just contains a child, as well as being able to be sized.
|
||||||
///
|
///
|
||||||
@ -38,14 +40,19 @@ impl<'a, Message> Container<'a, Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message> TmpComponent<Message> for Container<'a, Message> {
|
impl<'a, Message> TmpComponent<Message> for Container<'a, Message> {
|
||||||
fn draw<B>(&mut self, context: DrawContext<'_>, _frame: &mut Frame<'_, B>)
|
fn draw<B>(
|
||||||
where
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>,
|
||||||
|
_frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>, _event: Event,
|
||||||
|
_messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,9 @@ use tui::{backend::Backend, layout::Rect, Frame};
|
|||||||
pub mod flex_element;
|
pub mod flex_element;
|
||||||
pub use flex_element::FlexElement;
|
pub use flex_element::FlexElement;
|
||||||
|
|
||||||
use crate::tuine::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent};
|
use crate::tuine::{
|
||||||
|
Bounds, DrawContext, Element, Event, LayoutNode, Size, StateContext, Status, TmpComponent,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Axis {
|
pub enum Axis {
|
||||||
@ -84,21 +86,26 @@ impl<'a, Message> Flex<'a, Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message> TmpComponent<Message> for Flex<'a, Message> {
|
impl<'a, Message> TmpComponent<Message> for Flex<'a, Message> {
|
||||||
fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
|
fn draw<B>(
|
||||||
where
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>,
|
||||||
|
frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
self.children
|
self.children
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.zip(context.children())
|
.zip(draw_ctx.children())
|
||||||
.for_each(|(child, child_node)| {
|
.for_each(|(child, child_node)| {
|
||||||
if child_node.should_draw() {
|
if child_node.should_draw() {
|
||||||
child.draw(child_node, frame);
|
child.draw(state_ctx, child_node, frame);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>, event: Event,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
// FIXME: On event for flex
|
// FIXME: On event for flex
|
||||||
|
|
||||||
Status::Ignored
|
Status::Ignored
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use tui::{backend::Backend, layout::Rect, Frame};
|
use tui::{backend::Backend, Frame};
|
||||||
|
|
||||||
use crate::tuine::{Bounds, DrawContext, Element, Event, LayoutNode, Size, Status, TmpComponent};
|
use crate::tuine::{
|
||||||
|
Bounds, DrawContext, Element, Event, LayoutNode, Size, StateContext, Status, TmpComponent,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Axis;
|
use super::Axis;
|
||||||
|
|
||||||
@ -37,17 +39,20 @@ impl<'a, Message> FlexElement<'a, Message> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
|
pub(crate) fn draw<B>(
|
||||||
where
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>,
|
||||||
|
frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
self.element.draw(context, frame)
|
self.element.draw(state_ctx, draw_ctx, frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn on_event(
|
pub(crate) fn on_event(
|
||||||
&mut self, area: Rect, event: Event, messages: &mut Vec<Message>,
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>, event: Event,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
) -> Status {
|
) -> Status {
|
||||||
self.element.on_event(area, event, messages)
|
self.element.on_event(state_ctx, draw_ctx, event, messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assumes the flex is 0. Just calls layout on its child.
|
/// Assumes the flex is 0. Just calls layout on its child.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use tui::{backend::Backend, layout::Rect, Frame};
|
use tui::{backend::Backend, Frame};
|
||||||
|
|
||||||
use crate::tuine::{DrawContext, Event, Status, TmpComponent};
|
use crate::tuine::{DrawContext, Event, StateContext, Status, TmpComponent};
|
||||||
|
|
||||||
/// A [`Component`] to handle keyboard shortcuts and assign actions to them.
|
/// A [`Component`] to handle keyboard shortcuts and assign actions to them.
|
||||||
///
|
///
|
||||||
@ -8,14 +8,19 @@ use crate::tuine::{DrawContext, Event, Status, TmpComponent};
|
|||||||
pub struct Shortcut {}
|
pub struct Shortcut {}
|
||||||
|
|
||||||
impl<Message> TmpComponent<Message> for Shortcut {
|
impl<Message> TmpComponent<Message> for Shortcut {
|
||||||
fn draw<B>(&mut self, _context: DrawContext<'_>, _frame: &mut Frame<'_, B>)
|
fn draw<B>(
|
||||||
where
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>,
|
||||||
|
_frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, _area: Rect, _event: Event, _messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, _state_ctx: &mut StateContext<'_>, _draw_ctx: DrawContext<'_>, _event: Event,
|
||||||
|
_messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
Status::Ignored
|
Status::Ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::TABLE_GAP_HEIGHT_LIMIT,
|
constants::TABLE_GAP_HEIGHT_LIMIT,
|
||||||
tuine::{DrawContext, Event, Status, TmpComponent, ViewContext},
|
tuine::{DrawContext, Event, Key, StateContext, Status, TmpComponent, ViewContext},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use self::table_column::{TextColumn, TextColumnConstraint};
|
pub use self::table_column::{TextColumn, TextColumnConstraint};
|
||||||
@ -29,7 +29,7 @@ pub struct StyleSheet {
|
|||||||
|
|
||||||
/// A sortable, scrollable table for text data.
|
/// A sortable, scrollable table for text data.
|
||||||
pub struct TextTable<'a, Message> {
|
pub struct TextTable<'a, Message> {
|
||||||
test_state: &'a mut TextTableState,
|
key: Key,
|
||||||
state: TextTableState,
|
state: TextTableState,
|
||||||
column_widths: Vec<u16>,
|
column_widths: Vec<u16>,
|
||||||
columns: Vec<TextColumn>,
|
columns: Vec<TextColumn>,
|
||||||
@ -46,10 +46,8 @@ pub struct TextTable<'a, Message> {
|
|||||||
impl<'a, Message> TextTable<'a, Message> {
|
impl<'a, Message> TextTable<'a, Message> {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new<S: Into<Cow<'static, str>>>(ctx: &mut ViewContext<'_>, columns: Vec<S>) -> Self {
|
pub fn new<S: Into<Cow<'static, str>>>(ctx: &mut ViewContext<'_>, columns: Vec<S>) -> Self {
|
||||||
let test_state = ctx.state::<TextTableState>(Location::caller());
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
test_state,
|
key: ctx.register_component(Location::caller()),
|
||||||
state: TextTableState::default(),
|
state: TextTableState::default(),
|
||||||
column_widths: vec![0; columns.len()],
|
column_widths: vec![0; columns.len()],
|
||||||
columns: columns
|
columns: columns
|
||||||
@ -169,11 +167,14 @@ impl<'a, Message> TextTable<'a, Message> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
|
impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
|
||||||
fn draw<B>(&mut self, context: DrawContext<'_>, frame: &mut Frame<'_, B>)
|
fn draw<B>(
|
||||||
where
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>,
|
||||||
|
frame: &mut Frame<'_, B>,
|
||||||
|
) where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
let rect = context.rect();
|
let rect = draw_ctx.rect();
|
||||||
|
let state = state_ctx.mut_state::<TextTableState>(self.key);
|
||||||
|
|
||||||
self.table_gap = if !self.show_gap
|
self.table_gap = if !self.show_gap
|
||||||
|| (self.rows.len() + 2 > rect.height.into() && rect.height < TABLE_GAP_HEIGHT_LIMIT)
|
|| (self.rows.len() + 2 > rect.height.into() && rect.height < TABLE_GAP_HEIGHT_LIMIT)
|
||||||
@ -198,10 +199,8 @@ impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
|
|||||||
// as well as truncating some entries based on available width.
|
// as well as truncating some entries based on available width.
|
||||||
let data_slice = {
|
let data_slice = {
|
||||||
// Note: `get_list_start` already ensures `start` is within the bounds of the number of items, so no need to check!
|
// Note: `get_list_start` already ensures `start` is within the bounds of the number of items, so no need to check!
|
||||||
let start = self
|
let start = state.display_start_index(rect, scrollable_height as usize);
|
||||||
.state
|
let end = min(state.num_items(), start + scrollable_height as usize);
|
||||||
.display_start_index(rect, scrollable_height as usize);
|
|
||||||
let end = min(self.state.num_items(), start + scrollable_height as usize);
|
|
||||||
|
|
||||||
self.rows[start..end].to_vec()
|
self.rows[start..end].to_vec()
|
||||||
};
|
};
|
||||||
@ -219,13 +218,19 @@ impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
|
|||||||
table = table.highlight_style(self.style_sheet.selected_text);
|
table = table.highlight_style(self.style_sheet.selected_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.render_stateful_widget(table.widths(&widths), rect, self.state.tui_state());
|
frame.render_stateful_widget(table.widths(&widths), rect, state.tui_state());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, area: Rect, event: Event, messages: &mut Vec<Message>) -> Status {
|
fn on_event(
|
||||||
|
&mut self, state_ctx: &mut StateContext<'_>, draw_ctx: DrawContext<'_>, event: Event,
|
||||||
|
messages: &mut Vec<Message>,
|
||||||
|
) -> Status {
|
||||||
use crate::tuine::MouseBoundIntersect;
|
use crate::tuine::MouseBoundIntersect;
|
||||||
use crossterm::event::{MouseButton, MouseEventKind};
|
use crossterm::event::{MouseButton, MouseEventKind};
|
||||||
|
|
||||||
|
let rect = draw_ctx.rect();
|
||||||
|
let state = state_ctx.mut_state::<TextTableState>(self.key);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::Keyboard(key_event) => {
|
Event::Keyboard(key_event) => {
|
||||||
if key_event.modifiers.is_empty() {
|
if key_event.modifiers.is_empty() {
|
||||||
@ -237,10 +242,10 @@ impl<'a, Message> TmpComponent<Message> for TextTable<'a, Message> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse_event) => {
|
Event::Mouse(mouse_event) => {
|
||||||
if mouse_event.does_mouse_intersect_bounds(area) {
|
if mouse_event.does_mouse_intersect_bounds(rect) {
|
||||||
match mouse_event.kind {
|
match mouse_event.kind {
|
||||||
MouseEventKind::Down(MouseButton::Left) => {
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
let y = mouse_event.row - area.top();
|
let y = mouse_event.row - rect.top();
|
||||||
|
|
||||||
if self.sortable && y == 0 {
|
if self.sortable && y == 0 {
|
||||||
todo!()
|
todo!()
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
use std::{panic::Location, rc::Rc};
|
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use tui::layout::Rect;
|
|
||||||
|
|
||||||
use super::{Key, LayoutNode, State};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct StateMap(FxHashMap<Key, (Rc<Box<dyn State>>, bool)>);
|
|
||||||
|
|
||||||
impl StateMap {
|
|
||||||
pub fn state<S: State + Default + 'static>(&mut self, key: Key) -> Rc<Box<dyn State>> {
|
|
||||||
let state = self
|
|
||||||
.0
|
|
||||||
.entry(key)
|
|
||||||
.or_insert_with(|| (Rc::new(Box::new(S::default())), true));
|
|
||||||
|
|
||||||
state.1 = true;
|
|
||||||
|
|
||||||
state.0.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ViewContext<'a> {
|
|
||||||
key_counter: usize,
|
|
||||||
state_map: &'a mut StateMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ViewContext<'a> {
|
|
||||||
pub fn new(state_map: &'a mut StateMap) -> Self {
|
|
||||||
Self {
|
|
||||||
key_counter: 0,
|
|
||||||
state_map,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn state<S: State + Default + 'static>(
|
|
||||||
&mut self, location: &'static Location<'static>,
|
|
||||||
) -> Rc<Box<dyn State>> {
|
|
||||||
let key = Key::new(location, self.key_counter);
|
|
||||||
self.key_counter += 1;
|
|
||||||
self.state_map.state::<S>(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DrawContext<'a> {
|
|
||||||
current_node: &'a LayoutNode,
|
|
||||||
current_offset: (u16, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DrawContext<'_> {
|
|
||||||
/// Creates a new [`DrawContext`], with the offset set to `(0, 0)`.
|
|
||||||
pub(crate) fn root(root: &'a LayoutNode) -> DrawContext<'a> {
|
|
||||||
DrawContext {
|
|
||||||
current_node: root,
|
|
||||||
current_offset: (0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn rect(&self) -> Rect {
|
|
||||||
let mut rect = self.current_node.rect;
|
|
||||||
rect.x += self.current_offset.0;
|
|
||||||
rect.y += self.current_offset.1;
|
|
||||||
|
|
||||||
rect
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn should_draw(&self) -> bool {
|
|
||||||
self.current_node.rect.area() != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn children(&self) -> impl Iterator<Item = DrawContext<'_>> {
|
|
||||||
let new_offset = (
|
|
||||||
self.current_offset.0 + self.current_node.rect.x,
|
|
||||||
self.current_offset.1 + self.current_node.rect.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.current_node
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.map(move |layout_node| DrawContext {
|
|
||||||
current_node: layout_node,
|
|
||||||
current_offset: new_offset,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
45
src/tuine/context/draw_context.rs
Normal file
45
src/tuine/context/draw_context.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use tui::layout::Rect;
|
||||||
|
|
||||||
|
use crate::tuine::LayoutNode;
|
||||||
|
|
||||||
|
pub struct DrawContext<'a> {
|
||||||
|
current_node: &'a LayoutNode,
|
||||||
|
current_offset: (u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DrawContext<'a> {
|
||||||
|
/// Creates a new [`DrawContext`], with the offset set to `(0, 0)`.
|
||||||
|
pub(crate) fn root(root: &'a LayoutNode) -> DrawContext<'a> {
|
||||||
|
DrawContext {
|
||||||
|
current_node: root,
|
||||||
|
current_offset: (0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn rect(&self) -> Rect {
|
||||||
|
let mut rect = self.current_node.rect;
|
||||||
|
rect.x += self.current_offset.0;
|
||||||
|
rect.y += self.current_offset.1;
|
||||||
|
|
||||||
|
rect
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn should_draw(&self) -> bool {
|
||||||
|
self.current_node.rect.area() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn children(&'a self) -> impl Iterator<Item = DrawContext<'_>> {
|
||||||
|
let new_offset = (
|
||||||
|
self.current_offset.0 + self.current_node.rect.x,
|
||||||
|
self.current_offset.1 + self.current_node.rect.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.current_node
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(move |layout_node| DrawContext {
|
||||||
|
current_node: layout_node,
|
||||||
|
current_offset: new_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
11
src/tuine/context/mod.rs
Normal file
11
src/tuine/context/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
pub mod state_map;
|
||||||
|
pub use state_map::StateMap;
|
||||||
|
|
||||||
|
pub mod draw_context;
|
||||||
|
pub use draw_context::DrawContext;
|
||||||
|
|
||||||
|
pub mod view_context;
|
||||||
|
pub use view_context::ViewContext;
|
||||||
|
|
||||||
|
pub mod state_context;
|
||||||
|
pub use state_context::StateContext;
|
19
src/tuine/context/state_context.rs
Normal file
19
src/tuine/context/state_context.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use crate::tuine::{Key, State, StateMap};
|
||||||
|
|
||||||
|
pub struct StateContext<'a> {
|
||||||
|
state_map: &'a mut StateMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StateContext<'a> {
|
||||||
|
pub fn new(state_map: &'a mut StateMap) -> Self {
|
||||||
|
Self { state_map }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state<S: State + Default + 'static>(&mut self, key: Key) -> &S {
|
||||||
|
self.state_map.state::<S>(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mut_state<S: State + Default + 'static>(&mut self, key: Key) -> &mut S {
|
||||||
|
self.state_map.mut_state::<S>(key)
|
||||||
|
}
|
||||||
|
}
|
38
src/tuine/context/state_map.rs
Normal file
38
src/tuine/context/state_map.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::tuine::{Key, State};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct StateMap(FxHashMap<Key, (Box<dyn State>, bool)>);
|
||||||
|
|
||||||
|
impl StateMap {
|
||||||
|
pub fn state<S: State + Default + 'static>(&mut self, key: Key) -> &S {
|
||||||
|
let state = self
|
||||||
|
.0
|
||||||
|
.entry(key)
|
||||||
|
.or_insert_with(|| (Box::new(S::default()), true));
|
||||||
|
|
||||||
|
state.1 = true;
|
||||||
|
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref()
|
||||||
|
.expect("Successful downcast of state.")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mut_state<S: State + Default + 'static>(&mut self, key: Key) -> &mut S {
|
||||||
|
let state = self
|
||||||
|
.0
|
||||||
|
.entry(key)
|
||||||
|
.or_insert_with(|| (Box::new(S::default()), true));
|
||||||
|
|
||||||
|
state.1 = true;
|
||||||
|
|
||||||
|
state
|
||||||
|
.0
|
||||||
|
.as_mut_any()
|
||||||
|
.downcast_mut()
|
||||||
|
.expect("Successful downcast of state.")
|
||||||
|
}
|
||||||
|
}
|
30
src/tuine/context/view_context.rs
Normal file
30
src/tuine/context/view_context.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::tuine::{Caller, Key, State, StateMap};
|
||||||
|
|
||||||
|
use super::StateContext;
|
||||||
|
|
||||||
|
pub struct ViewContext<'a> {
|
||||||
|
key_counter: usize,
|
||||||
|
state_context: StateContext<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ViewContext<'a> {
|
||||||
|
pub fn new(state_map: &'a mut StateMap) -> Self {
|
||||||
|
Self {
|
||||||
|
key_counter: 0,
|
||||||
|
state_context: StateContext::new(state_map),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_component<C: Into<Caller>>(&mut self, caller: C) -> Key {
|
||||||
|
self.key_counter += 1;
|
||||||
|
Key::new(caller.into(), self.key_counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state<S: State + Default + 'static>(&mut self, key: Key) -> &S {
|
||||||
|
self.state_context.state(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mut_state<S: State + Default + 'static>(&mut self, key: Key) -> &mut S {
|
||||||
|
self.state_context.mut_state(key)
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use tui::{layout::Rect, Frame};
|
use tui::Frame;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Block, Bounds, Carousel, Container, DrawContext, Event, Flex, LayoutNode, Shortcut, Size,
|
Block, Bounds, Carousel, Container, DrawContext, Event, Flex, LayoutNode, Shortcut, Size,
|
||||||
Status, TextTable, TmpComponent,
|
StateContext, Status, TextTable, TmpComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An [`Element`] is an instantiated [`Component`].
|
/// An [`Element`] is an instantiated [`Component`].
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use tui::{backend::Backend, Terminal};
|
||||||
use tui::{backend::Backend, layout::Rect, Terminal};
|
|
||||||
|
|
||||||
use crate::tuine::Status;
|
use crate::tuine::Status;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
build_layout_tree, Application, Element, Event, Key, State, StateMap, TmpComponent, ViewContext,
|
build_layout_tree, Application, DrawContext, Element, Event, LayoutNode, StateContext,
|
||||||
|
StateMap, TmpComponent, ViewContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@ -29,10 +29,11 @@ where
|
|||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
let mut app_data = AppData::default();
|
let mut app_data = AppData::default();
|
||||||
|
let mut layout: LayoutNode = LayoutNode::default();
|
||||||
|
|
||||||
let mut user_interface = {
|
let mut user_interface = {
|
||||||
let mut ctx = ViewContext::new(&mut app_data.state_map);
|
let mut ui = new_user_interface(&mut application, &mut app_data);
|
||||||
let mut ui = application.view(&mut ctx);
|
draw(&mut ui, terminal, &mut app_data, &mut layout)?;
|
||||||
draw(&mut ui, terminal)?;
|
|
||||||
ui
|
ui
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,24 +41,15 @@ where
|
|||||||
if let Ok(event) = receiver.recv() {
|
if let Ok(event) = receiver.recv() {
|
||||||
match event {
|
match event {
|
||||||
RuntimeEvent::UserInterface(event) => {
|
RuntimeEvent::UserInterface(event) => {
|
||||||
let mut messages = vec![];
|
on_event(
|
||||||
|
&mut application,
|
||||||
let rect = Rect::default(); // FIXME: TEMP
|
&mut user_interface,
|
||||||
match user_interface.on_event(rect, event, &mut messages) {
|
&mut app_data,
|
||||||
Status::Captured => {}
|
&mut layout,
|
||||||
Status::Ignored => {
|
event,
|
||||||
application.global_event_handler(event, &mut messages);
|
);
|
||||||
}
|
user_interface = new_user_interface(&mut application, &mut app_data);
|
||||||
}
|
draw(&mut user_interface, terminal, &mut app_data, &mut layout)?;
|
||||||
|
|
||||||
for msg in messages {
|
|
||||||
debug!("Message: {:?}", msg); // FIXME: Remove this debug line!
|
|
||||||
application.update(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ctx = ViewContext::new(&mut app_data.state_map);
|
|
||||||
user_interface = application.view(&mut ctx);
|
|
||||||
draw(&mut user_interface, terminal)?;
|
|
||||||
}
|
}
|
||||||
RuntimeEvent::Custom(message) => {
|
RuntimeEvent::Custom(message) => {
|
||||||
application.update(message);
|
application.update(message);
|
||||||
@ -66,10 +58,9 @@ where
|
|||||||
width: _,
|
width: _,
|
||||||
height: _,
|
height: _,
|
||||||
} => {
|
} => {
|
||||||
let mut ctx = ViewContext::new(&mut app_data.state_map);
|
user_interface = new_user_interface(&mut application, &mut app_data);
|
||||||
user_interface = application.view(&mut ctx);
|
|
||||||
// FIXME: Also nuke any cache and the like...
|
// FIXME: Also nuke any cache and the like...
|
||||||
draw(&mut user_interface, terminal)?;
|
draw(&mut user_interface, terminal, &mut app_data, &mut layout)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -77,21 +68,60 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
application.destroy();
|
application.destructor();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw<M, B>(user_interface: &mut Element<'_, M>, terminal: &mut Terminal<B>) -> anyhow::Result<()>
|
fn on_event<A>(
|
||||||
|
application: &mut A, user_interface: &mut Element<'_, A::Message>, app_data: &mut AppData,
|
||||||
|
layout: &mut LayoutNode, event: Event,
|
||||||
|
) where
|
||||||
|
A: Application + 'static,
|
||||||
|
{
|
||||||
|
let mut messages = vec![];
|
||||||
|
let mut state_ctx = StateContext::new(&mut app_data.state_map);
|
||||||
|
let draw_ctx = DrawContext::root(&layout);
|
||||||
|
|
||||||
|
match user_interface.on_event(&mut state_ctx, draw_ctx, event, &mut messages) {
|
||||||
|
Status::Captured => {
|
||||||
|
// TODO: What to do on capture?
|
||||||
|
}
|
||||||
|
Status::Ignored => {
|
||||||
|
application.global_event_handler(event, &mut messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for msg in messages {
|
||||||
|
debug!("Message: {:?}", msg); // FIXME: Remove this debug line!
|
||||||
|
application.update(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_user_interface<A>(
|
||||||
|
application: &mut A, app_data: &mut AppData,
|
||||||
|
) -> Element<'static, A::Message>
|
||||||
|
where
|
||||||
|
A: Application + 'static,
|
||||||
|
{
|
||||||
|
let mut ctx = ViewContext::new(&mut app_data.state_map);
|
||||||
|
application.view(&mut ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw<M, B>(
|
||||||
|
user_interface: &mut Element<'_, M>, terminal: &mut Terminal<B>, app_data: &mut AppData,
|
||||||
|
layout: &mut LayoutNode,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
B: Backend,
|
B: Backend,
|
||||||
{
|
{
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
let rect = frame.size();
|
let rect = frame.size();
|
||||||
let layout = build_layout_tree(rect, &user_interface);
|
*layout = build_layout_tree(rect, &user_interface);
|
||||||
let context = super::DrawContext::root(&layout);
|
let mut state_ctx = StateContext::new(&mut app_data.state_map);
|
||||||
|
let draw_ctx = DrawContext::root(&layout);
|
||||||
|
|
||||||
user_interface.draw(context, frame);
|
user_interface.draw(&mut state_ctx, draw_ctx, frame);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user