photon_ui/lib.rs
1//! photon-ui — Blazing fast minimal TUI
2//!
3//! This crate provides a lightweight, high-performance terminal UI framework
4//! built on top of [`crossterm`]. It features a custom differential renderer,
5//! ANSI-aware text wrapping, OSC 8 hyperlink support, and a component-based
6//! architecture.
7//!
8//! # Quick start
9//!
10//! ```no_run
11//! use photon_ui::{
12//! Component,
13//! RenderError,
14//! Rendered,
15//! TUI,
16//! TestTerminal,
17//! components::Text,
18//! };
19//!
20//! let mut tui = TUI::new(Box::new(TestTerminal::new(80, 24)));
21//! tui.mount(Box::new(Text::new("Hello, world!", 0, 0)));
22//! tui.render_frame().unwrap();
23//! ```
24
25#![deny(dead_code)]
26#![deny(unused)]
27#![deny(unused_mut)]
28#![deny(clippy::missing_safety_doc)]
29#![deny(clippy::undocumented_unsafe_blocks)]
30#![cfg_attr(not(test), deny(clippy::expect_used))]
31#![cfg_attr(not(test), deny(clippy::unwrap_used))]
32// for @siennathesane's sanity and to make it clear the scope of error handling. and because it's
33// super fucking subtle and i'll miss it in code reviews sorry not sorry
34#![deny(clippy::question_mark_used)]
35// just keeps syntax consistent
36#![deny(clippy::needless_borrow)]
37// personal preference.
38#![allow(bindings_with_variant_name)]
39
40pub mod autocomplete;
41pub mod components;
42pub mod events;
43pub mod fuzzy;
44pub mod image;
45pub mod keybindings;
46pub mod kill_ring;
47pub mod layout;
48pub mod renderer;
49pub mod terminal;
50pub mod theme;
51pub mod tui;
52pub mod undo_stack;
53pub mod utils;
54pub mod word_navigation;
55
56pub use crossterm::event::KeyEvent;
57pub use events::{
58 Event,
59 Key,
60 Modifiers,
61 matches_key,
62};
63pub use keybindings::{
64 KeybindingsManager,
65 default_bindings,
66};
67pub use renderer::{
68 InputResult,
69 RenderError,
70 RenderStrategy,
71 Rendered,
72 Renderer,
73};
74pub use terminal::{
75 Terminal,
76 TestTerminal,
77};
78pub use tui::{
79 Anchor,
80 Overlay,
81 OverlayConstraints,
82 OverlayPosition,
83 TUI,
84};
85
86/// A UI element that can be rendered and respond to input.
87///
88/// All visible elements in a TUI application implement this trait. The
89/// framework calls [`render`](Component::render) on every frame and
90/// [`handle_input`](Component::handle_input) when the focused component should
91/// process an event.
92///
93/// Components that can receive focus should also implement [`Focusable`].
94pub trait Component {
95 /// Render this component into lines of text at the given width.
96 ///
97 /// The returned [`Rendered`] must satisfy the invariant that every line's
98 /// visible width is ≤ `width`.
99 fn render(&self, width: u16) -> Result<Rendered, RenderError>;
100
101 /// Render this component into a specific rectangular area.
102 ///
103 /// The default implementation delegates to [`render`](Component::render)
104 /// with the rect's width, ignoring height bounds. Components that want
105 /// to be layout-aware (e.g. clip to height, scroll, center vertically)
106 /// should override this.
107 fn render_rect(&self, rect: crate::layout::Rect) -> Result<Rendered, RenderError> {
108 self.render(rect.width)
109 }
110
111 /// Handle an input event (key press, resize, mouse, etc.).
112 ///
113 /// The default implementation ignores all events. Override this to add
114 /// interactivity.
115 fn handle_input(&mut self, _event: &events::Event) -> InputResult {
116 InputResult::Ignored
117 }
118
119 /// Returns `true` if this component wants to receive
120 /// `KeyEventKind::Release` events in addition to `Press` / `Repeat`.
121 ///
122 /// Most components should leave this as `false`.
123 fn wants_key_release(&self) -> bool {
124 false
125 }
126
127 /// Cast this component to a [`Focusable`] reference, if supported.
128 fn as_focusable(&self) -> Option<&dyn Focusable> {
129 None
130 }
131
132 /// Cast this component to a mutable [`Focusable`] reference, if supported.
133 fn as_focusable_mut(&mut self) -> Option<&mut dyn Focusable> {
134 None
135 }
136}
137
138/// Extension of [`Component`] for elements that can receive keyboard focus.
139///
140/// Focus is managed by [`TUI`]; only the focused component receives input
141/// events.
142pub trait Focusable: Component {
143 /// Returns `true` when this component currently has focus.
144 fn focused(&self) -> bool;
145
146 /// Set or clear the focused state.
147 fn set_focused(&mut self, focused: bool);
148}