Zi is a library for building modern terminal user interfaces. A user interface in Zi is built as a tree of stateful components. Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. The [`App`](app/struct.App.html) runtime keeps track of components as they are mounted, updated and eventually removed and only calls `view()` on those UI components that have changed and have to be re-rendered. Lower level and independent of the components, the terminal backend will incrementally redraw only those parts of the screen that have changed. # A Basic Example The following is a complete example of a Zi application which implements a counter. It should provide a good sample of the different [`Component`](trait.Component.html) methods and how they fit together. A slightly more complex version which includes styling can be found at `examples/counter.rs`. ![zi-counter-example](https://user-images.githubusercontent.com/797170/137802270-0a4a50af-1fd5-473f-a52c-9d3a107809d0.gif) Anyone familiar with Yew, Elm or React + Redux should be familiar with all the high-level concepts. Moreover, the names of some types and functions are the same as in `Yew`. ```rust use zi::{ components::{ border::{Border, BorderProperties}, text::{Text, TextAlign, TextProperties}, }, prelude::*, }; use zi_term::Result; // Message type handled by the `Counter` component. enum Message { Increment, Decrement, } // The `Counter` component. struct Counter { // The state of the component -- the current value of the counter. count: usize, // A `ComponentLink` allows us to send messages to the component in reaction // to user input as well as to gracefully exit. link: ComponentLink, } // Components implement the `Component` trait and are the building blocks of the // UI in Zi. The trait describes stateful components and their lifecycle. impl Component for Counter { // Messages are used to make components dynamic and interactive. For simple // or pure components, this will be `()`. Complex, stateful components will // typically use an enum to declare multiple Message types. In this case, we // will emit two kinds of message (`Increment` or `Decrement`) in reaction // to user input. type Message = Message; // Properties are the inputs to a Component passed in by their parent. type Properties = (); // Creates ("mounts") a new `Counter` component. fn create( _properties: Self::Properties, _frame: Rect, link: ComponentLink, ) -> Self { Self { count: 0, link } } // Returns the current visual layout of the component. // - The `Border` component wraps a component and draws a border around it. // - The `Text` component displays some text. fn view(&self) -> Layout { Border::with(BorderProperties::new(Text::with( TextProperties::new() .align(TextAlign::Centre) .content(format!("Counter: {}", self.count)), ))) } // Components handle messages in their `update` method and commonly use this // method to update their state and (optionally) re-render themselves. fn update(&mut self, message: Self::Message) -> ShouldRender { self.count = match message { Message::Increment => self.count.saturating_add(1), Message::Decrement => self.count.saturating_sub(1), }; ShouldRender::Yes } // Updates the key bindings of the component. // // This method will be called after the component lifecycle methods. It is // used to specify how to react in response to keyboard events, typically // by sending a message. fn bindings(&self, bindings: &mut Bindings) { // If we already initialised the bindings, nothing to do -- they never // change in this example if !bindings.is_empty() { return; } // Set focus to `true` in order to react to key presses bindings.set_focus(true); // Increment bindings.add("increment", [Key::Char('+')], || Message::Increment); bindings.add("increment", [Key::Char('=')], || Message::Increment); // Decrement bindings.add("decrement", [Key::Char('-')], || Message::Decrement); // Exit bindings.add("exit", [Key::Ctrl('c')], |this: &Self| this.link.exit()); bindings.add("exit", [Key::Esc], |this: &Self| this.link.exit()); } } fn main() -> zi_term::Result<()> { zi_term::incremental()?.run_event_loop(Counter::with(())) } ``` More examples can be found in the `examples` directory of the git repository. # License This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.