Better event handling and structure

This commit is contained in:
Joshua Barretto 2024-03-04 16:51:57 +00:00
parent f7943884cb
commit a58caed9e9
7 changed files with 101 additions and 39 deletions

View file

@ -2,6 +2,7 @@ mod action;
mod ui; mod ui;
mod terminal; mod terminal;
mod state; mod state;
mod theme;
use crate::{ use crate::{
terminal::{Terminal, TerminalEvent, Color}, terminal::{Terminal, TerminalEvent, Color},

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
theme,
ui::{self, Resp, Element as _}, ui::{self, Resp, Element as _},
Action, Event, Args, Error, Color, Action, Event, Args, Error, Color,
}; };
@ -41,24 +42,10 @@ pub enum Activity {
Console(Console), Console(Console),
} }
pub struct Theme {
pub ui_bg: Color,
pub status_bg: Color,
}
impl Default for Theme {
fn default() -> Self {
Self {
ui_bg: Color::AnsiValue(235),
status_bg: Color::AnsiValue(23),
}
}
}
pub struct State { pub struct State {
pub activities: HopSlotMap<ActivityId, Activity>, pub activities: HopSlotMap<ActivityId, Activity>,
pub tick: u64, pub tick: u64,
pub theme: Theme, pub theme: theme::Theme,
} }
impl TryFrom<Args> for State { impl TryFrom<Args> for State {
@ -67,7 +54,7 @@ impl TryFrom<Args> for State {
Ok(Self { Ok(Self {
activities: HopSlotMap::default(), activities: HopSlotMap::default(),
tick: 0, tick: 0,
theme: Theme::default(), theme: theme::Theme::default(),
}) })
} }
} }

View file

@ -1,4 +1,4 @@
use super::Error; use crate::{theme, Error};
pub use crossterm::{ pub use crossterm::{
cursor::SetCursorStyle as CursorStyle, cursor::SetCursorStyle as CursorStyle,
@ -72,6 +72,23 @@ impl<'a> Rect<'a> {
} }
} }
pub fn with_border(&mut self, theme: &theme::BorderTheme) -> Rect {
let edge = self.size().map(|e| e.saturating_sub(1));
for col in 0..edge[0] {
self.get_mut([col, 0]).map(|c| c.c = theme.top);
self.get_mut([col, edge[1]]).map(|c| c.c = theme.bottom);
}
for row in 0..edge[1] {
self.get_mut([0, row]).map(|c| c.c = theme.left);
self.get_mut([edge[0], row]).map(|c| c.c = theme.right);
}
self.get_mut([0, 0]).map(|c| c.c = theme.top_left);
self.get_mut([edge[0], 0]).map(|c| c.c = theme.top_right);
self.get_mut([0, edge[1]]).map(|c| c.c = theme.bottom_left);
self.get_mut([edge[0], edge[1]]).map(|c| c.c = theme.bottom_right);
self.rect([1, 1], self.size().map(|e| e.saturating_sub(2)))
}
pub fn with_fg(&mut self, fg: Color) -> Rect { pub fn with_fg(&mut self, fg: Color) -> Rect {
Rect { fg, bg: self.bg, origin: self.origin, size: self.size, fb: self.fb } Rect { fg, bg: self.bg, origin: self.origin, size: self.size, fb: self.fb }
} }
@ -82,17 +99,17 @@ impl<'a> Rect<'a> {
pub fn size(&self) -> [usize; 2] { self.size.map(|e| e as usize) } pub fn size(&self) -> [usize; 2] { self.size.map(|e| e as usize) }
pub fn fill(&mut self, c: char) -> &mut Self { pub fn fill(&mut self, c: char) -> Rect {
for row in 0..self.size()[1] { for row in 0..self.size()[1] {
for col in 0..self.size()[0] { for col in 0..self.size()[0] {
let cell = Cell { c, fg: self.fg, bg: self.bg }; let cell = Cell { c, fg: self.fg, bg: self.bg };
if let Some(c) = self.get_mut([col, row]) { *c = cell; } if let Some(c) = self.get_mut([col, row]) { *c = cell; }
} }
} }
self self.rect([0, 0], self.size())
} }
pub fn text<C: Borrow<char>>(&mut self, origin: [usize; 2], text: impl IntoIterator<Item = C>) -> &mut Self { pub fn text<C: Borrow<char>>(&mut self, origin: [usize; 2], text: impl IntoIterator<Item = C>) -> Rect {
for (idx, c) in text.into_iter().enumerate() { for (idx, c) in text.into_iter().enumerate() {
if origin[0] + idx >= self.size()[0] { if origin[0] + idx >= self.size()[0] {
break; break;
@ -101,15 +118,15 @@ impl<'a> Rect<'a> {
if let Some(c) = self.get_mut([origin[0] + idx, origin[1]]) { *c = cell; } if let Some(c) = self.get_mut([origin[0] + idx, origin[1]]) { *c = cell; }
} }
} }
self self.rect([0, 0], self.size())
} }
pub fn set_cursor(&mut self, cursor: [usize; 2], style: CursorStyle) -> &mut Self { pub fn set_cursor(&mut self, cursor: [usize; 2], style: CursorStyle) -> Rect {
self.fb.cursor = Some(( self.fb.cursor = Some((
[self.origin[0] + cursor[0] as u16, self.origin[1] + cursor[1] as u16], [self.origin[0] + cursor[0] as u16, self.origin[1] + cursor[1] as u16],
style, style,
)); ));
self self.rect([0, 0], self.size())
} }
} }

43
src/theme.rs Normal file
View file

@ -0,0 +1,43 @@
use crate::Color;
pub struct BorderTheme {
pub left: char,
pub right: char,
pub top: char,
pub bottom: char,
pub top_left: char,
pub top_right: char,
pub bottom_left: char,
pub bottom_right: char,
}
impl Default for BorderTheme {
fn default() -> Self {
Self {
left: '',
right: '',
top: '',
bottom: '',
top_left: '',
top_right: '',
bottom_left: '',
bottom_right: '',
}
}
}
pub struct Theme {
pub ui_bg: Color,
pub status_bg: Color,
pub border: BorderTheme,
}
impl Default for Theme {
fn default() -> Self {
Self {
ui_bg: Color::AnsiValue(235),
status_bg: Color::AnsiValue(23),
border: BorderTheme::default(),
}
}
}

View file

@ -1,3 +1,12 @@
use super::*; use super::*;
pub struct Panes; pub struct Panes;
impl Element for Panes {
fn handle(&mut self, event: Event) -> Result<Resp, Event> {
match event.to_action(|e| None) {
//Some(_) => todo!(),
_ => Err(event),
}
}
}

View file

@ -59,8 +59,6 @@ impl Element<CanEnd> for Prompt {
impl Visual for Prompt { impl Visual for Prompt {
fn render(&self, state: &State, frame: &mut Rect) { fn render(&self, state: &State, frame: &mut Rect) {
frame frame
.rect([0, frame.size()[1].saturating_sub(1)], [frame.size()[0], 1])
.with_bg(state.theme.status_bg)
.with(|f| self.input.render(state, f)); .with(|f| self.input.render(state, f));
} }
} }
@ -87,7 +85,7 @@ impl Visual for Show {
let lines = self.label.lines().count(); let lines = self.label.lines().count();
self.label.render( self.label.render(
state, state,
&mut frame.rect([0, frame.size()[1].saturating_sub(1 + lines)], [frame.size()[0], lines]), &mut frame.rect([0, frame.size()[1].saturating_sub(3 + lines)], [frame.size()[0], lines]),
); );
} }
} }
@ -118,7 +116,7 @@ impl Visual for Confirm {
let lines = self.label.lines().count(); let lines = self.label.lines().count();
self.label.render( self.label.render(
state, state,
&mut frame.rect([0, frame.size()[1].saturating_sub(1 + lines)], [frame.size()[0], lines]), &mut frame.rect([0, frame.size()[1].saturating_sub(3 + lines)], [frame.size()[0], lines]),
); );
} }
} }

View file

@ -29,13 +29,16 @@ impl Element<CanEnd> for Root {
let action = loop { let action = loop {
task_idx = match task_idx.checked_sub(1) { task_idx = match task_idx.checked_sub(1) {
Some(task_idx) => task_idx, Some(task_idx) => task_idx,
None => break event.to_action(|e| if e.is_prompt() { None => break match self.panes.handle(event) {
Some(Action::OpenPrompt) Ok(resp) => resp.action,
} else if e.is_cancel() { Err(event) => event.to_action(|e| if e.is_prompt() {
Some(Action::Cancel) Some(Action::OpenPrompt)
} else { } else if e.is_cancel() {
None Some(Action::Cancel)
}), } else {
None
}),
},
}; };
let res = match &mut self.tasks[task_idx] { let res = match &mut self.tasks[task_idx] {
@ -83,20 +86,24 @@ impl Element<CanEnd> for Root {
impl Visual for Root { impl Visual for Root {
fn render(&self, state: &State, frame: &mut Rect) { fn render(&self, state: &State, frame: &mut Rect) {
frame frame
.with_bg(Color::Black)
.fill(' '); .fill(' ');
// Display status bar // Display status bar
frame frame
.rect([0, frame.size()[1].saturating_sub(1)], [frame.size()[0], 1]) .rect([0, frame.size()[1].saturating_sub(3)], [frame.size()[0], 3])
.with_bg(state.theme.status_bg) .fill(' ')
.fill(' '); .with_border(&state.theme.border)
.with(|frame| {
if let Some(Task::Prompt(p)) = self.tasks.last() {
p.render(state, frame);
}
});
if let Some(task) = self.tasks.last() { if let Some(task) = self.tasks.last() {
match task { match task {
Task::Prompt(p) => p.render(state, frame),
Task::Show(s) => s.render(state, frame), Task::Show(s) => s.render(state, frame),
Task::Confirm(c) => c.render(state, frame), Task::Confirm(c) => c.render(state, frame),
_ => {},
} }
} }
} }