add: realm boilerplate, copy of demo
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
//! ## Terminal
|
||||
//!
|
||||
//! terminal helper
|
||||
|
||||
pub use super::*;
|
||||
|
||||
pub mod model;
|
||||
@@ -0,0 +1,190 @@
|
||||
//! ## Model
|
||||
//!
|
||||
//! app model
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::props::{Alignment, Color, TextModifiers};
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalAdapter, TerminalBridge};
|
||||
use tuirealm::{
|
||||
Application, AttrValue, Attribute, EventListenerCfg, Sub, SubClause, SubEventClause, Update,
|
||||
};
|
||||
|
||||
use super::components::{Clock, DigitCounter, Label, LetterCounter};
|
||||
use super::{Id, Msg};
|
||||
|
||||
pub struct Model<T>
|
||||
where
|
||||
T: TerminalAdapter,
|
||||
{
|
||||
/// Application
|
||||
pub app: Application<Id, Msg, NoUserEvent>,
|
||||
/// Indicates that the application must quit
|
||||
pub quit: bool,
|
||||
/// Tells whether to redraw interface
|
||||
pub redraw: bool,
|
||||
/// Used to draw to terminal
|
||||
pub terminal: TerminalBridge<T>,
|
||||
}
|
||||
|
||||
impl Default for Model<CrosstermTerminalAdapter> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
app: Self::init_app(),
|
||||
quit: false,
|
||||
redraw: true,
|
||||
terminal: TerminalBridge::init_crossterm().expect("Cannot initialize terminal"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Model<T>
|
||||
where
|
||||
T: TerminalAdapter,
|
||||
{
|
||||
pub fn view(&mut self) {
|
||||
assert!(self
|
||||
.terminal
|
||||
.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(3), // Clock
|
||||
Constraint::Length(3), // Letter Counter
|
||||
Constraint::Length(3), // Digit Counter
|
||||
Constraint::Length(1), // Label
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.area());
|
||||
self.app.view(&Id::Clock, f, chunks[0]);
|
||||
self.app.view(&Id::LetterCounter, f, chunks[1]);
|
||||
self.app.view(&Id::DigitCounter, f, chunks[2]);
|
||||
self.app.view(&Id::Label, f, chunks[3]);
|
||||
})
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
fn init_app() -> Application<Id, Msg, NoUserEvent> {
|
||||
// Setup application
|
||||
// NOTE: NoUserEvent is a shorthand to tell tui-realm we're not going to use any custom user event
|
||||
// NOTE: the event listener is configured to use the default crossterm input listener and to raise a Tick event each second
|
||||
// which we will use to update the clock
|
||||
|
||||
let mut app: Application<Id, Msg, NoUserEvent> = Application::init(
|
||||
EventListenerCfg::default()
|
||||
.crossterm_input_listener(Duration::from_millis(20), 3)
|
||||
.poll_timeout(Duration::from_millis(10))
|
||||
.tick_interval(Duration::from_secs(1)),
|
||||
);
|
||||
// Mount components
|
||||
assert!(app
|
||||
.mount(
|
||||
Id::Label,
|
||||
Box::new(
|
||||
Label::default()
|
||||
.text("Waiting for a Msg...")
|
||||
.alignment(Alignment::Left)
|
||||
.background(Color::Reset)
|
||||
.foreground(Color::LightYellow)
|
||||
.modifiers(TextModifiers::BOLD),
|
||||
),
|
||||
Vec::default(),
|
||||
)
|
||||
.is_ok());
|
||||
// Mount clock, subscribe to tick
|
||||
assert!(app
|
||||
.mount(
|
||||
Id::Clock,
|
||||
Box::new(
|
||||
Clock::new(SystemTime::now())
|
||||
.alignment(Alignment::Center)
|
||||
.background(Color::Reset)
|
||||
.foreground(Color::Cyan)
|
||||
.modifiers(TextModifiers::BOLD)
|
||||
),
|
||||
vec![Sub::new(SubEventClause::Tick, SubClause::Always)]
|
||||
)
|
||||
.is_ok());
|
||||
// Mount counters
|
||||
assert!(app
|
||||
.mount(
|
||||
Id::LetterCounter,
|
||||
Box::new(LetterCounter::new(0)),
|
||||
Vec::new()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(app
|
||||
.mount(
|
||||
Id::DigitCounter,
|
||||
Box::new(DigitCounter::new(5)),
|
||||
Vec::default()
|
||||
)
|
||||
.is_ok());
|
||||
// Active letter counter
|
||||
assert!(app.active(&Id::LetterCounter).is_ok());
|
||||
app
|
||||
}
|
||||
}
|
||||
|
||||
// Let's implement Update for model
|
||||
|
||||
impl<T> Update<Msg> for Model<T>
|
||||
where
|
||||
T: TerminalAdapter,
|
||||
{
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
if let Some(msg) = msg {
|
||||
// Set redraw
|
||||
self.redraw = true;
|
||||
// Match message
|
||||
match msg {
|
||||
Msg::AppClose => {
|
||||
self.quit = true; // Terminate
|
||||
None
|
||||
}
|
||||
Msg::Clock => None,
|
||||
Msg::DigitCounterBlur => {
|
||||
// Give focus to letter counter
|
||||
assert!(self.app.active(&Id::LetterCounter).is_ok());
|
||||
None
|
||||
}
|
||||
Msg::DigitCounterChanged(v) => {
|
||||
// Update label
|
||||
assert!(self
|
||||
.app
|
||||
.attr(
|
||||
&Id::Label,
|
||||
Attribute::Text,
|
||||
AttrValue::String(format!("DigitCounter has now value: {}", v))
|
||||
)
|
||||
.is_ok());
|
||||
None
|
||||
}
|
||||
Msg::LetterCounterBlur => {
|
||||
// Give focus to digit counter
|
||||
assert!(self.app.active(&Id::DigitCounter).is_ok());
|
||||
None
|
||||
}
|
||||
Msg::LetterCounterChanged(v) => {
|
||||
// Update label
|
||||
assert!(self
|
||||
.app
|
||||
.attr(
|
||||
&Id::Label,
|
||||
Attribute::Text,
|
||||
AttrValue::String(format!("LetterCounter has now value: {}", v))
|
||||
)
|
||||
.is_ok());
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//! ## Label
|
||||
//!
|
||||
//! label component
|
||||
|
||||
use std::ops::Add;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{Alignment, Color, TextModifiers};
|
||||
use tuirealm::ratatui::layout::Rect;
|
||||
use tuirealm::{
|
||||
AttrValue, Attribute, Component, Event, Frame, MockComponent, NoUserEvent, State, StateValue,
|
||||
};
|
||||
|
||||
use super::{Label, Msg};
|
||||
|
||||
/// Simple clock component which displays current time
|
||||
pub struct Clock {
|
||||
component: Label,
|
||||
states: OwnStates,
|
||||
}
|
||||
|
||||
impl Clock {
|
||||
pub fn new(initial_time: SystemTime) -> Self {
|
||||
Self {
|
||||
component: Label::default(),
|
||||
states: OwnStates::new(initial_time),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alignment(mut self, a: Alignment) -> Self {
|
||||
self.component
|
||||
.attr(Attribute::TextAlign, AttrValue::Alignment(a));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn foreground(mut self, c: Color) -> Self {
|
||||
self.component
|
||||
.attr(Attribute::Foreground, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn background(mut self, c: Color) -> Self {
|
||||
self.component
|
||||
.attr(Attribute::Background, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn modifiers(mut self, m: TextModifiers) -> Self {
|
||||
self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
|
||||
self
|
||||
}
|
||||
|
||||
fn time_to_str(&self) -> String {
|
||||
let since_the_epoch = self.get_epoch_time();
|
||||
let hours = (since_the_epoch / 3600) % 24;
|
||||
let minutes = (since_the_epoch / 60) % 60;
|
||||
let seconds = since_the_epoch % 60;
|
||||
format!("{:02}:{:02}:{:02}", hours, minutes, seconds)
|
||||
}
|
||||
|
||||
fn get_epoch_time(&self) -> u64 {
|
||||
self.states
|
||||
.time
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards")
|
||||
.as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for Clock {
|
||||
fn view(&mut self, frame: &mut Frame, area: Rect) {
|
||||
// Render
|
||||
self.component.view(frame, area);
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.component.query(attr)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
self.component.attr(attr, value);
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
// Return current time
|
||||
State::One(StateValue::U64(self.get_epoch_time()))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
self.component.perform(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Clock {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
if let Event::Tick = ev {
|
||||
self.states.tick();
|
||||
// Set text
|
||||
self.attr(Attribute::Text, AttrValue::String(self.time_to_str()));
|
||||
Some(Msg::Clock)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OwnStates {
|
||||
time: SystemTime,
|
||||
}
|
||||
|
||||
impl OwnStates {
|
||||
pub fn new(time: SystemTime) -> Self {
|
||||
Self { time }
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
self.time = self.time.add(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
//! ## Label
|
||||
//!
|
||||
//! label component
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, Borders, Color, Style, TextModifiers};
|
||||
use tuirealm::ratatui::layout::Rect;
|
||||
use tuirealm::ratatui::widgets::{BorderType, Paragraph};
|
||||
use tuirealm::{
|
||||
AttrValue, Attribute, Component, Event, Frame, MockComponent, NoUserEvent, Props, State,
|
||||
StateValue,
|
||||
};
|
||||
|
||||
use super::{get_block, Msg};
|
||||
|
||||
/// Counter which increments its value on Submit
|
||||
struct Counter {
|
||||
props: Props,
|
||||
states: OwnStates,
|
||||
}
|
||||
|
||||
impl Default for Counter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
props: Props::default(),
|
||||
states: OwnStates::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Counter {
|
||||
pub fn label<S>(mut self, label: S) -> Self
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.attr(
|
||||
Attribute::Title,
|
||||
AttrValue::Title((label.as_ref().to_string(), Alignment::Center)),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn value(mut self, n: isize) -> Self {
|
||||
self.attr(Attribute::Value, AttrValue::Number(n));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alignment(mut self, a: Alignment) -> Self {
|
||||
self.attr(Attribute::TextAlign, AttrValue::Alignment(a));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn foreground(mut self, c: Color) -> Self {
|
||||
self.attr(Attribute::Foreground, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn background(mut self, c: Color) -> Self {
|
||||
self.attr(Attribute::Background, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn modifiers(mut self, m: TextModifiers) -> Self {
|
||||
self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn borders(mut self, b: Borders) -> Self {
|
||||
self.attr(Attribute::Borders, AttrValue::Borders(b));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for Counter {
|
||||
fn view(&mut self, frame: &mut Frame, area: Rect) {
|
||||
// Check if visible
|
||||
if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
|
||||
// Get properties
|
||||
let text = self.states.counter.to_string();
|
||||
let alignment = self
|
||||
.props
|
||||
.get_or(Attribute::TextAlign, AttrValue::Alignment(Alignment::Left))
|
||||
.unwrap_alignment();
|
||||
let foreground = self
|
||||
.props
|
||||
.get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
let background = self
|
||||
.props
|
||||
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
let modifiers = self
|
||||
.props
|
||||
.get_or(
|
||||
Attribute::TextProps,
|
||||
AttrValue::TextModifiers(TextModifiers::empty()),
|
||||
)
|
||||
.unwrap_text_modifiers();
|
||||
let title = self
|
||||
.props
|
||||
.get_or(
|
||||
Attribute::Title,
|
||||
AttrValue::Title((String::default(), Alignment::Center)),
|
||||
)
|
||||
.unwrap_title();
|
||||
let borders = self
|
||||
.props
|
||||
.get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
|
||||
.unwrap_borders();
|
||||
let focus = self
|
||||
.props
|
||||
.get_or(Attribute::Focus, AttrValue::Flag(false))
|
||||
.unwrap_flag();
|
||||
frame.render_widget(
|
||||
Paragraph::new(text)
|
||||
.block(get_block(borders, title, focus))
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(foreground)
|
||||
.bg(background)
|
||||
.add_modifier(modifiers),
|
||||
)
|
||||
.alignment(alignment),
|
||||
area,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.props.get(attr)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
self.props.set(attr, value);
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::One(StateValue::Isize(self.states.counter))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
match cmd {
|
||||
Cmd::Submit => {
|
||||
self.states.incr();
|
||||
CmdResult::Changed(self.state())
|
||||
}
|
||||
_ => CmdResult::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OwnStates {
|
||||
counter: isize,
|
||||
}
|
||||
|
||||
impl Default for OwnStates {
|
||||
fn default() -> Self {
|
||||
Self { counter: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnStates {
|
||||
fn incr(&mut self) {
|
||||
self.counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// -- Counter components
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct LetterCounter {
|
||||
component: Counter,
|
||||
}
|
||||
|
||||
impl LetterCounter {
|
||||
pub fn new(initial_value: isize) -> Self {
|
||||
Self {
|
||||
component: Counter::default()
|
||||
.alignment(Alignment::Center)
|
||||
.background(Color::Reset)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::LightGreen)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::LightGreen)
|
||||
.modifiers(TextModifiers::BOLD)
|
||||
.value(initial_value)
|
||||
.label("Letter counter"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for LetterCounter {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
// Get command
|
||||
let cmd = match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) if ch.is_alphabetic() => Cmd::Submit,
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Tab,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => return Some(Msg::LetterCounterBlur), // Return focus lost
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => return Some(Msg::AppClose),
|
||||
_ => Cmd::None,
|
||||
};
|
||||
// perform
|
||||
match self.perform(cmd) {
|
||||
CmdResult::Changed(State::One(StateValue::Isize(c))) => {
|
||||
Some(Msg::LetterCounterChanged(c))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
pub struct DigitCounter {
|
||||
component: Counter,
|
||||
}
|
||||
|
||||
impl DigitCounter {
|
||||
pub fn new(initial_value: isize) -> Self {
|
||||
Self {
|
||||
component: Counter::default()
|
||||
.alignment(Alignment::Center)
|
||||
.background(Color::Reset)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Yellow)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::Yellow)
|
||||
.modifiers(TextModifiers::BOLD)
|
||||
.value(initial_value)
|
||||
.label("Digit counter"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DigitCounter {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
// Get command
|
||||
let cmd = match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) if ch.is_digit(10) => Cmd::Submit,
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Tab,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => return Some(Msg::DigitCounterBlur), // Return focus lost
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => return Some(Msg::AppClose),
|
||||
_ => Cmd::None,
|
||||
};
|
||||
// perform
|
||||
match self.perform(cmd) {
|
||||
CmdResult::Changed(State::One(StateValue::Isize(c))) => {
|
||||
Some(Msg::DigitCounterChanged(c))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
//! ## Label
|
||||
//!
|
||||
//! label component
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{Alignment, Color, Style, TextModifiers};
|
||||
use tuirealm::ratatui::layout::Rect;
|
||||
use tuirealm::ratatui::widgets::Paragraph;
|
||||
use tuirealm::{
|
||||
AttrValue, Attribute, Component, Event, Frame, MockComponent, NoUserEvent, Props, State,
|
||||
};
|
||||
|
||||
use super::Msg;
|
||||
|
||||
/// Simple label component; just renders a text
|
||||
/// NOTE: since I need just one label, I'm not going to use different object; I will directly implement Component for Label.
|
||||
/// This is not ideal actually and in a real app you should differentiate Mock Components from Application Components.
|
||||
pub struct Label {
|
||||
props: Props,
|
||||
}
|
||||
|
||||
impl Default for Label {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
props: Props::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Label {
|
||||
pub fn text<S>(mut self, s: S) -> Self
|
||||
where
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.attr(Attribute::Text, AttrValue::String(s.as_ref().to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alignment(mut self, a: Alignment) -> Self {
|
||||
self.attr(Attribute::TextAlign, AttrValue::Alignment(a));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn foreground(mut self, c: Color) -> Self {
|
||||
self.attr(Attribute::Foreground, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn background(mut self, c: Color) -> Self {
|
||||
self.attr(Attribute::Background, AttrValue::Color(c));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn modifiers(mut self, m: TextModifiers) -> Self {
|
||||
self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for Label {
|
||||
fn view(&mut self, frame: &mut Frame, area: Rect) {
|
||||
// Check if visible
|
||||
if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
|
||||
// Get properties
|
||||
let text = self
|
||||
.props
|
||||
.get_or(Attribute::Text, AttrValue::String(String::default()))
|
||||
.unwrap_string();
|
||||
let alignment = self
|
||||
.props
|
||||
.get_or(Attribute::TextAlign, AttrValue::Alignment(Alignment::Left))
|
||||
.unwrap_alignment();
|
||||
let foreground = self
|
||||
.props
|
||||
.get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
let background = self
|
||||
.props
|
||||
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
let modifiers = self
|
||||
.props
|
||||
.get_or(
|
||||
Attribute::TextProps,
|
||||
AttrValue::TextModifiers(TextModifiers::empty()),
|
||||
)
|
||||
.unwrap_text_modifiers();
|
||||
frame.render_widget(
|
||||
Paragraph::new(text)
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(foreground)
|
||||
.bg(background)
|
||||
.add_modifier(modifiers),
|
||||
)
|
||||
.alignment(alignment),
|
||||
area,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.props.get(attr)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
self.props.set(attr, value);
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::None
|
||||
}
|
||||
|
||||
fn perform(&mut self, _: Cmd) -> CmdResult {
|
||||
CmdResult::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Label {
|
||||
fn on(&mut self, _: Event<NoUserEvent>) -> Option<Msg> {
|
||||
// Does nothing
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//! ## Components
|
||||
//!
|
||||
//! demo example components
|
||||
|
||||
use tuirealm::props::{Alignment, Borders, Color, Style};
|
||||
use tuirealm::ratatui::widgets::Block;
|
||||
|
||||
use super::Msg;
|
||||
|
||||
// -- modules
|
||||
mod clock;
|
||||
mod counter;
|
||||
mod label;
|
||||
|
||||
// -- export
|
||||
pub use clock::Clock;
|
||||
pub use counter::{DigitCounter, LetterCounter};
|
||||
pub use label::Label;
|
||||
|
||||
/// ### get_block
|
||||
///
|
||||
/// Get block
|
||||
pub(crate) fn get_block<'a>(props: Borders, title: (String, Alignment), focus: bool) -> Block<'a> {
|
||||
Block::default()
|
||||
.borders(props.sides)
|
||||
.border_style(match focus {
|
||||
true => props.style(),
|
||||
false => Style::default().fg(Color::Reset).bg(Color::Reset),
|
||||
})
|
||||
.border_type(props.modifiers)
|
||||
.title(title.0)
|
||||
.title_alignment(title.1)
|
||||
}
|
||||
+76
-2
@@ -1,3 +1,77 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
//! ## Demo
|
||||
//!
|
||||
//! `Demo` shows how to use tui-realm in a real case
|
||||
|
||||
extern crate tuirealm;
|
||||
|
||||
use tuirealm::application::PollStrategy;
|
||||
use tuirealm::{AttrValue, Attribute, Update};
|
||||
// -- internal
|
||||
mod app;
|
||||
mod components;
|
||||
use app::model::Model;
|
||||
|
||||
// Let's define the messages handled by our app. NOTE: it must derive `PartialEq`
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Msg {
|
||||
AppClose,
|
||||
Clock,
|
||||
DigitCounterChanged(isize),
|
||||
DigitCounterBlur,
|
||||
LetterCounterChanged(isize),
|
||||
LetterCounterBlur,
|
||||
}
|
||||
|
||||
// Let's define the component ids for our application
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum Id {
|
||||
Clock,
|
||||
DigitCounter,
|
||||
LetterCounter,
|
||||
Label,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Setup model
|
||||
let mut model = Model::default();
|
||||
// Enter alternate screen
|
||||
let _ = model.terminal.enter_alternate_screen();
|
||||
let _ = model.terminal.enable_raw_mode();
|
||||
// Main loop
|
||||
// NOTE: loop until quit; quit is set in update if AppClose is received from counter
|
||||
while !model.quit {
|
||||
// Tick
|
||||
match model.app.tick(PollStrategy::Once) {
|
||||
Err(err) => {
|
||||
assert!(model
|
||||
.app
|
||||
.attr(
|
||||
&Id::Label,
|
||||
Attribute::Text,
|
||||
AttrValue::String(format!("Application error: {}", err)),
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
Ok(messages) if messages.len() > 0 => {
|
||||
// NOTE: redraw if at least one msg has been processed
|
||||
model.redraw = true;
|
||||
for msg in messages.into_iter() {
|
||||
let mut msg = Some(msg);
|
||||
while msg.is_some() {
|
||||
msg = model.update(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Redraw
|
||||
if model.redraw {
|
||||
model.view();
|
||||
model.redraw = false;
|
||||
}
|
||||
}
|
||||
// Terminate terminal
|
||||
let _ = model.terminal.leave_alternate_screen();
|
||||
let _ = model.terminal.disable_raw_mode();
|
||||
let _ = model.terminal.clear_screen();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user