pub struct App { /* private fields */ }Expand description
Running retained-UI terminal host.
Implementations§
Source§impl App
impl App
Sourcepub const fn ui(&self) -> &Ui
pub const fn ui(&self) -> &Ui
Borrows the retained UI.
Examples found in repository?
112async fn main() -> io::Result<()> {
113 let mut app = AppOptions::new()
114 .mouse()
115 .quit([Key::Ctrl('c'), Key::Ctrl('q')])
116 .start(|env| build_ui(env.viewport, env.ctx))
117 .await?;
118 // The picker tab opens with the first model's details, like the chat
119 // overlay does.
120 picker::show_detail_on(app.ui_mut(), Some(0));
121
122 let mut synced = String::new();
123 let mut lab = anim::Lab::new();
124 let mut layers = Layers::default();
125 let mut next_step = tokio::time::Instant::now() + anim::AUTOPLAY_STEP;
126
127 loop {
128 let event = tokio::select! {
129 event = app.next() => match event? {
130 Some(event) => event,
131 None => break,
132 },
133 () = tokio::time::sleep_until(next_step) => {
134 if lab.autoplay && active_tab(app.ui()) == "Anim" {
135 lab.advance(app.ui_mut());
136 }
137 next_step += anim::AUTOPLAY_STEP;
138 continue;
139 },
140 };
141 match event {
142 AppEvent::Resized(viewport) => {
143 for pane in render::PANE_IDS {
144 app.ui_mut().set_height(pane, render::pane_height(viewport));
145 }
146 },
147 AppEvent::Key(key) => match active_tab(app.ui()).as_str() {
148 "Anim" => lab.handle_key(key, app.ui_mut()),
149 "Overlay" => match key {
150 Key::Ctrl('k') if layers.picker.is_none() => {
151 layers.picker = Some(overlay::show_picker(app.ui_mut()));
152 },
153 Key::Ctrl('g') => match layers.help.take() {
154 Some(id) => {
155 app.ui_mut().close_overlay(id);
156 },
157 None => layers.help = Some(overlay::show_help(app.ui_mut())),
158 },
159 _ => {},
160 },
161 _ => {},
162 },
163 // The Overlay tab's modal select committed a model.
164 AppEvent::Changed { id, value } if id == "model" => {
165 if let Some(overlay) = layers.picker.take() {
166 let label = overlay::MODELS
167 .iter()
168 .find(|(short, ..)| *short == value)
169 .map_or(value.as_str(), |(_, label, _)| label);
170 app.ui_mut().set_text("status", format!("model: {label}"));
171 app.ui_mut().close_overlay(overlay);
172 }
173 },
174 // The Picker tab's select moved: mirror the chat picker's
175 // facts-and-chips detail line.
176 AppEvent::Highlighted { id, value } if id == "models" => {
177 picker::show_detail_on(app.ui_mut(), value.as_str().parse().ok());
178 },
179 AppEvent::Filtered { id, value, .. } if id == "models" => {
180 let model = value.and_then(|value| value.as_str().parse().ok());
181 picker::show_detail_on(app.ui_mut(), model);
182 },
183 AppEvent::OverlayClosed(id) => {
184 if layers.picker == Some(id) {
185 layers.picker = None;
186 }
187 if layers.help == Some(id) {
188 layers.help = None;
189 }
190 },
191 _ => {},
192 }
193 render::sync_preview(app.ui_mut(), &mut synced);
194 // Reserve the Overlay tab's chords only while it is showing, so the
195 // focused composer can't spend Ctrl+K on kill-line — and the Live
196 // tab's editor keeps it.
197 let chords: &[Key] = if active_tab(app.ui()) == "Overlay" {
198 &[Key::Ctrl('k'), Key::Ctrl('g')]
199 } else {
200 &[]
201 };
202 app.set_hotkeys(chords.iter().copied());
203 }
204 Ok(())
205}Sourcepub const fn ui_mut(&mut self) -> &mut Ui
pub const fn ui_mut(&mut self) -> &mut Ui
Mutably borrows the retained UI between host events.
Examples found in repository?
156fn show_stats(app: &mut App, chosen: Option<&str>) {
157 let caps = app.caps();
158 let stats = app.last_stats();
159 let pixels = caps.cell_px.map_or_else(
160 || "cell-px ?".to_owned(),
161 |(width, height)| format!("cell-px {width}×{height}"),
162 );
163 let login = chosen.map_or(String::new(), |id| format!("login: {id} · "));
164 app.ui_mut().set_text(
165 HUD_ID,
166 format!(
167 "{login}{} · {} · {} · repaint: {} cells",
168 graphics_label(caps.graphics),
169 caps.id,
170 pixels,
171 stats.changed_cells,
172 ),
173 );
174}
175
176fn forced_from_args(caps: &TerminalCaps) -> Option<Graphics> {
177 let mut forced = None;
178 for argument in std::env::args().skip(1) {
179 forced = match argument.as_str() {
180 "--cells" => Some(Graphics::Cells),
181 "--kitty" if caps.kitty_placeholders => Some(Graphics::KittyPlaceholders),
182 "--kitty" => Some(Graphics::KittyDirect),
183 "--kitty-placeholders" => Some(Graphics::KittyPlaceholders),
184 "--sixel" => Some(Graphics::Sixel),
185 _ => forced,
186 };
187 }
188 forced
189}
190
191#[tokio::main]
192async fn main() -> io::Result<()> {
193 let mut app = AppOptions::new()
194 .mouse()
195 .probe(Duration::from_millis(150))
196 .graphics_with(forced_from_args)
197 .start(|env| build_ui(env.viewport, env.ctx))
198 .await?;
199 if app.caps().graphics != Graphics::Cells {
200 for (index, provider) in PROVIDERS.iter().enumerate() {
201 let png = tokio::fs::read(format!("{ASSET_DIR}/{}.png", provider.id)).await?;
202 app.renderer_mut().register_image(
203 u32::try_from(index + 1).expect("provider count fits image IDs"),
204 png,
205 )?;
206 }
207 app.ui_mut().invalidate(SCROLL_ID);
208 }
209 show_stats(&mut app, None);
210 let mut chosen: Option<String> = None;
211 while let Some(event) = app.next().await? {
212 match event {
213 AppEvent::Resized(viewport) => {
214 app.ui_mut().set_height(SCROLL_ID, scroll_height(viewport));
215 },
216 AppEvent::Pressed(id) => chosen = Some(id.to_string()),
217 _ => {},
218 }
219 show_stats(&mut app, chosen.as_deref());
220 }
221 Ok(())
222}More examples
112async fn main() -> io::Result<()> {
113 let mut app = AppOptions::new()
114 .mouse()
115 .quit([Key::Ctrl('c'), Key::Ctrl('q')])
116 .start(|env| build_ui(env.viewport, env.ctx))
117 .await?;
118 // The picker tab opens with the first model's details, like the chat
119 // overlay does.
120 picker::show_detail_on(app.ui_mut(), Some(0));
121
122 let mut synced = String::new();
123 let mut lab = anim::Lab::new();
124 let mut layers = Layers::default();
125 let mut next_step = tokio::time::Instant::now() + anim::AUTOPLAY_STEP;
126
127 loop {
128 let event = tokio::select! {
129 event = app.next() => match event? {
130 Some(event) => event,
131 None => break,
132 },
133 () = tokio::time::sleep_until(next_step) => {
134 if lab.autoplay && active_tab(app.ui()) == "Anim" {
135 lab.advance(app.ui_mut());
136 }
137 next_step += anim::AUTOPLAY_STEP;
138 continue;
139 },
140 };
141 match event {
142 AppEvent::Resized(viewport) => {
143 for pane in render::PANE_IDS {
144 app.ui_mut().set_height(pane, render::pane_height(viewport));
145 }
146 },
147 AppEvent::Key(key) => match active_tab(app.ui()).as_str() {
148 "Anim" => lab.handle_key(key, app.ui_mut()),
149 "Overlay" => match key {
150 Key::Ctrl('k') if layers.picker.is_none() => {
151 layers.picker = Some(overlay::show_picker(app.ui_mut()));
152 },
153 Key::Ctrl('g') => match layers.help.take() {
154 Some(id) => {
155 app.ui_mut().close_overlay(id);
156 },
157 None => layers.help = Some(overlay::show_help(app.ui_mut())),
158 },
159 _ => {},
160 },
161 _ => {},
162 },
163 // The Overlay tab's modal select committed a model.
164 AppEvent::Changed { id, value } if id == "model" => {
165 if let Some(overlay) = layers.picker.take() {
166 let label = overlay::MODELS
167 .iter()
168 .find(|(short, ..)| *short == value)
169 .map_or(value.as_str(), |(_, label, _)| label);
170 app.ui_mut().set_text("status", format!("model: {label}"));
171 app.ui_mut().close_overlay(overlay);
172 }
173 },
174 // The Picker tab's select moved: mirror the chat picker's
175 // facts-and-chips detail line.
176 AppEvent::Highlighted { id, value } if id == "models" => {
177 picker::show_detail_on(app.ui_mut(), value.as_str().parse().ok());
178 },
179 AppEvent::Filtered { id, value, .. } if id == "models" => {
180 let model = value.and_then(|value| value.as_str().parse().ok());
181 picker::show_detail_on(app.ui_mut(), model);
182 },
183 AppEvent::OverlayClosed(id) => {
184 if layers.picker == Some(id) {
185 layers.picker = None;
186 }
187 if layers.help == Some(id) {
188 layers.help = None;
189 }
190 },
191 _ => {},
192 }
193 render::sync_preview(app.ui_mut(), &mut synced);
194 // Reserve the Overlay tab's chords only while it is showing, so the
195 // focused composer can't spend Ctrl+K on kill-line — and the Live
196 // tab's editor keeps it.
197 let chords: &[Key] = if active_tab(app.ui()) == "Overlay" {
198 &[Key::Ctrl('k'), Key::Ctrl('g')]
199 } else {
200 &[]
201 };
202 app.set_hotkeys(chords.iter().copied());
203 }
204 Ok(())
205}Sourcepub fn set_hotkeys(&mut self, chords: impl IntoIterator<Item = Key>)
pub fn set_hotkeys(&mut self, chords: impl IntoIterator<Item = Key>)
Replaces the reserved host chords, scoping AppOptions::hotkeys
to the screen that is actually showing.
Examples found in repository?
112async fn main() -> io::Result<()> {
113 let mut app = AppOptions::new()
114 .mouse()
115 .quit([Key::Ctrl('c'), Key::Ctrl('q')])
116 .start(|env| build_ui(env.viewport, env.ctx))
117 .await?;
118 // The picker tab opens with the first model's details, like the chat
119 // overlay does.
120 picker::show_detail_on(app.ui_mut(), Some(0));
121
122 let mut synced = String::new();
123 let mut lab = anim::Lab::new();
124 let mut layers = Layers::default();
125 let mut next_step = tokio::time::Instant::now() + anim::AUTOPLAY_STEP;
126
127 loop {
128 let event = tokio::select! {
129 event = app.next() => match event? {
130 Some(event) => event,
131 None => break,
132 },
133 () = tokio::time::sleep_until(next_step) => {
134 if lab.autoplay && active_tab(app.ui()) == "Anim" {
135 lab.advance(app.ui_mut());
136 }
137 next_step += anim::AUTOPLAY_STEP;
138 continue;
139 },
140 };
141 match event {
142 AppEvent::Resized(viewport) => {
143 for pane in render::PANE_IDS {
144 app.ui_mut().set_height(pane, render::pane_height(viewport));
145 }
146 },
147 AppEvent::Key(key) => match active_tab(app.ui()).as_str() {
148 "Anim" => lab.handle_key(key, app.ui_mut()),
149 "Overlay" => match key {
150 Key::Ctrl('k') if layers.picker.is_none() => {
151 layers.picker = Some(overlay::show_picker(app.ui_mut()));
152 },
153 Key::Ctrl('g') => match layers.help.take() {
154 Some(id) => {
155 app.ui_mut().close_overlay(id);
156 },
157 None => layers.help = Some(overlay::show_help(app.ui_mut())),
158 },
159 _ => {},
160 },
161 _ => {},
162 },
163 // The Overlay tab's modal select committed a model.
164 AppEvent::Changed { id, value } if id == "model" => {
165 if let Some(overlay) = layers.picker.take() {
166 let label = overlay::MODELS
167 .iter()
168 .find(|(short, ..)| *short == value)
169 .map_or(value.as_str(), |(_, label, _)| label);
170 app.ui_mut().set_text("status", format!("model: {label}"));
171 app.ui_mut().close_overlay(overlay);
172 }
173 },
174 // The Picker tab's select moved: mirror the chat picker's
175 // facts-and-chips detail line.
176 AppEvent::Highlighted { id, value } if id == "models" => {
177 picker::show_detail_on(app.ui_mut(), value.as_str().parse().ok());
178 },
179 AppEvent::Filtered { id, value, .. } if id == "models" => {
180 let model = value.and_then(|value| value.as_str().parse().ok());
181 picker::show_detail_on(app.ui_mut(), model);
182 },
183 AppEvent::OverlayClosed(id) => {
184 if layers.picker == Some(id) {
185 layers.picker = None;
186 }
187 if layers.help == Some(id) {
188 layers.help = None;
189 }
190 },
191 _ => {},
192 }
193 render::sync_preview(app.ui_mut(), &mut synced);
194 // Reserve the Overlay tab's chords only while it is showing, so the
195 // focused composer can't spend Ctrl+K on kill-line — and the Live
196 // tab's editor keeps it.
197 let chords: &[Key] = if active_tab(app.ui()) == "Overlay" {
198 &[Key::Ctrl('k'), Key::Ctrl('g')]
199 } else {
200 &[]
201 };
202 app.set_hotkeys(chords.iter().copied());
203 }
204 Ok(())
205}Sourcepub fn handle(&self) -> UiHandle
pub fn handle(&self) -> UiHandle
Creates a remote that can update or stop this host.
Examples found in repository?
42async fn main() -> io::Result<()> {
43 let path = std::env::args()
44 .nth(1)
45 .unwrap_or_else(|| "example.tml".into());
46 let source = std::fs::read_to_string(&path)?;
47
48 let mut ctx = None;
49 let mut app = AppOptions::new()
50 .quit([Key::Ctrl('c'), Key::Char('q'), Key::Esc])
51 .start(|env| {
52 let ui = build(&source, env.viewport.width, &env.ctx);
53 ctx = Some(env.ctx);
54 ui
55 })
56 .await?;
57 let ctx = ctx.expect("start ran the builder");
58
59 let handle = app.handle();
60 tokio::spawn(async move {
61 let mut seen = modified(path.as_ref());
62 loop {
63 tokio::time::sleep(Duration::from_millis(150)).await;
64 let stamp = modified(path.as_ref());
65 if stamp == seen {
66 continue;
67 }
68 seen = stamp;
69 let Ok(source) = std::fs::read_to_string(&path) else {
70 continue;
71 };
72 let ctx = ctx.clone();
73 handle.update(move |ui| *ui = build(&source, ui.frame().size().width, &ctx));
74 }
75 });
76
77 while app.next().await?.is_some() {}
78 Ok(())
79}Sourcepub const fn renderer_mut(&mut self) -> &mut Renderer<TtyOut>
pub const fn renderer_mut(&mut self) -> &mut Renderer<TtyOut>
Mutably borrows the renderer for image registration and output policy.
Examples found in repository?
192async fn main() -> io::Result<()> {
193 let mut app = AppOptions::new()
194 .mouse()
195 .probe(Duration::from_millis(150))
196 .graphics_with(forced_from_args)
197 .start(|env| build_ui(env.viewport, env.ctx))
198 .await?;
199 if app.caps().graphics != Graphics::Cells {
200 for (index, provider) in PROVIDERS.iter().enumerate() {
201 let png = tokio::fs::read(format!("{ASSET_DIR}/{}.png", provider.id)).await?;
202 app.renderer_mut().register_image(
203 u32::try_from(index + 1).expect("provider count fits image IDs"),
204 png,
205 )?;
206 }
207 app.ui_mut().invalidate(SCROLL_ID);
208 }
209 show_stats(&mut app, None);
210 let mut chosen: Option<String> = None;
211 while let Some(event) = app.next().await? {
212 match event {
213 AppEvent::Resized(viewport) => {
214 app.ui_mut().set_height(SCROLL_ID, scroll_height(viewport));
215 },
216 AppEvent::Pressed(id) => chosen = Some(id.to_string()),
217 _ => {},
218 }
219 show_stats(&mut app, chosen.as_deref());
220 }
221 Ok(())
222}Sourcepub const fn terminal_mut(&mut self) -> &mut Terminal
pub const fn terminal_mut(&mut self) -> &mut Terminal
Mutably borrows the terminal for titles, progress, and appearance hooks.
Sourcepub const fn caps(&self) -> TerminalCaps
pub const fn caps(&self) -> TerminalCaps
Returns the resolved terminal capabilities.
Examples found in repository?
156fn show_stats(app: &mut App, chosen: Option<&str>) {
157 let caps = app.caps();
158 let stats = app.last_stats();
159 let pixels = caps.cell_px.map_or_else(
160 || "cell-px ?".to_owned(),
161 |(width, height)| format!("cell-px {width}×{height}"),
162 );
163 let login = chosen.map_or(String::new(), |id| format!("login: {id} · "));
164 app.ui_mut().set_text(
165 HUD_ID,
166 format!(
167 "{login}{} · {} · {} · repaint: {} cells",
168 graphics_label(caps.graphics),
169 caps.id,
170 pixels,
171 stats.changed_cells,
172 ),
173 );
174}
175
176fn forced_from_args(caps: &TerminalCaps) -> Option<Graphics> {
177 let mut forced = None;
178 for argument in std::env::args().skip(1) {
179 forced = match argument.as_str() {
180 "--cells" => Some(Graphics::Cells),
181 "--kitty" if caps.kitty_placeholders => Some(Graphics::KittyPlaceholders),
182 "--kitty" => Some(Graphics::KittyDirect),
183 "--kitty-placeholders" => Some(Graphics::KittyPlaceholders),
184 "--sixel" => Some(Graphics::Sixel),
185 _ => forced,
186 };
187 }
188 forced
189}
190
191#[tokio::main]
192async fn main() -> io::Result<()> {
193 let mut app = AppOptions::new()
194 .mouse()
195 .probe(Duration::from_millis(150))
196 .graphics_with(forced_from_args)
197 .start(|env| build_ui(env.viewport, env.ctx))
198 .await?;
199 if app.caps().graphics != Graphics::Cells {
200 for (index, provider) in PROVIDERS.iter().enumerate() {
201 let png = tokio::fs::read(format!("{ASSET_DIR}/{}.png", provider.id)).await?;
202 app.renderer_mut().register_image(
203 u32::try_from(index + 1).expect("provider count fits image IDs"),
204 png,
205 )?;
206 }
207 app.ui_mut().invalidate(SCROLL_ID);
208 }
209 show_stats(&mut app, None);
210 let mut chosen: Option<String> = None;
211 while let Some(event) = app.next().await? {
212 match event {
213 AppEvent::Resized(viewport) => {
214 app.ui_mut().set_height(SCROLL_ID, scroll_height(viewport));
215 },
216 AppEvent::Pressed(id) => chosen = Some(id.to_string()),
217 _ => {},
218 }
219 show_stats(&mut app, chosen.as_deref());
220 }
221 Ok(())
222}Sourcepub const fn last_stats(&self) -> PaintStats
pub const fn last_stats(&self) -> PaintStats
Returns statistics from the most recent present or rebuild.
Examples found in repository?
156fn show_stats(app: &mut App, chosen: Option<&str>) {
157 let caps = app.caps();
158 let stats = app.last_stats();
159 let pixels = caps.cell_px.map_or_else(
160 || "cell-px ?".to_owned(),
161 |(width, height)| format!("cell-px {width}×{height}"),
162 );
163 let login = chosen.map_or(String::new(), |id| format!("login: {id} · "));
164 app.ui_mut().set_text(
165 HUD_ID,
166 format!(
167 "{login}{} · {} · {} · repaint: {} cells",
168 graphics_label(caps.graphics),
169 caps.id,
170 pixels,
171 stats.changed_cells,
172 ),
173 );
174}Sourcepub const fn set_stable_rows(&mut self, rows: u16)
pub const fn set_stable_rows(&mut self, rows: u16)
Sets the immutable leading-row boundary used for presentation.
Sourcepub const fn hold_alt(&mut self, hold: bool)
pub const fn hold_alt(&mut self, hold: bool)
Requests or releases a persistent alternate-screen hold.
Fullscreen scenes — a welcome screen, a pager — hold the alternate
screen for their lifetime: frames paint there with mouse tracking
active while the inline transcript stays untouched underneath. Every
visible modal overlay holds it automatically; this covers scenes
without one. Non-modal layers (crate::OverlayOptions::non_modal)
never hold: they composite into the live inline viewport while the
document keeps committing to native scrollback. Release restores the
main screen, rebuilding history only when geometry changed while
held. Takes effect on the next App::next call.
Sourcepub async fn next(&mut self) -> Result<Option<AppEvent>>
pub async fn next(&mut self) -> Result<Option<AppEvent>>
Flushes pending damage, waits for one host event, and routes it.
None means shutdown; subsequent calls continue returning None.
§Errors
Propagates terminal input, geometry, and renderer failures.
Examples found in repository?
192async fn main() -> io::Result<()> {
193 let mut app = AppOptions::new()
194 .mouse()
195 .probe(Duration::from_millis(150))
196 .graphics_with(forced_from_args)
197 .start(|env| build_ui(env.viewport, env.ctx))
198 .await?;
199 if app.caps().graphics != Graphics::Cells {
200 for (index, provider) in PROVIDERS.iter().enumerate() {
201 let png = tokio::fs::read(format!("{ASSET_DIR}/{}.png", provider.id)).await?;
202 app.renderer_mut().register_image(
203 u32::try_from(index + 1).expect("provider count fits image IDs"),
204 png,
205 )?;
206 }
207 app.ui_mut().invalidate(SCROLL_ID);
208 }
209 show_stats(&mut app, None);
210 let mut chosen: Option<String> = None;
211 while let Some(event) = app.next().await? {
212 match event {
213 AppEvent::Resized(viewport) => {
214 app.ui_mut().set_height(SCROLL_ID, scroll_height(viewport));
215 },
216 AppEvent::Pressed(id) => chosen = Some(id.to_string()),
217 _ => {},
218 }
219 show_stats(&mut app, chosen.as_deref());
220 }
221 Ok(())
222}More examples
42async fn main() -> io::Result<()> {
43 let path = std::env::args()
44 .nth(1)
45 .unwrap_or_else(|| "example.tml".into());
46 let source = std::fs::read_to_string(&path)?;
47
48 let mut ctx = None;
49 let mut app = AppOptions::new()
50 .quit([Key::Ctrl('c'), Key::Char('q'), Key::Esc])
51 .start(|env| {
52 let ui = build(&source, env.viewport.width, &env.ctx);
53 ctx = Some(env.ctx);
54 ui
55 })
56 .await?;
57 let ctx = ctx.expect("start ran the builder");
58
59 let handle = app.handle();
60 tokio::spawn(async move {
61 let mut seen = modified(path.as_ref());
62 loop {
63 tokio::time::sleep(Duration::from_millis(150)).await;
64 let stamp = modified(path.as_ref());
65 if stamp == seen {
66 continue;
67 }
68 seen = stamp;
69 let Ok(source) = std::fs::read_to_string(&path) else {
70 continue;
71 };
72 let ctx = ctx.clone();
73 handle.update(move |ui| *ui = build(&source, ui.frame().size().width, &ctx));
74 }
75 });
76
77 while app.next().await?.is_some() {}
78 Ok(())
79}112async fn main() -> io::Result<()> {
113 let mut app = AppOptions::new()
114 .mouse()
115 .quit([Key::Ctrl('c'), Key::Ctrl('q')])
116 .start(|env| build_ui(env.viewport, env.ctx))
117 .await?;
118 // The picker tab opens with the first model's details, like the chat
119 // overlay does.
120 picker::show_detail_on(app.ui_mut(), Some(0));
121
122 let mut synced = String::new();
123 let mut lab = anim::Lab::new();
124 let mut layers = Layers::default();
125 let mut next_step = tokio::time::Instant::now() + anim::AUTOPLAY_STEP;
126
127 loop {
128 let event = tokio::select! {
129 event = app.next() => match event? {
130 Some(event) => event,
131 None => break,
132 },
133 () = tokio::time::sleep_until(next_step) => {
134 if lab.autoplay && active_tab(app.ui()) == "Anim" {
135 lab.advance(app.ui_mut());
136 }
137 next_step += anim::AUTOPLAY_STEP;
138 continue;
139 },
140 };
141 match event {
142 AppEvent::Resized(viewport) => {
143 for pane in render::PANE_IDS {
144 app.ui_mut().set_height(pane, render::pane_height(viewport));
145 }
146 },
147 AppEvent::Key(key) => match active_tab(app.ui()).as_str() {
148 "Anim" => lab.handle_key(key, app.ui_mut()),
149 "Overlay" => match key {
150 Key::Ctrl('k') if layers.picker.is_none() => {
151 layers.picker = Some(overlay::show_picker(app.ui_mut()));
152 },
153 Key::Ctrl('g') => match layers.help.take() {
154 Some(id) => {
155 app.ui_mut().close_overlay(id);
156 },
157 None => layers.help = Some(overlay::show_help(app.ui_mut())),
158 },
159 _ => {},
160 },
161 _ => {},
162 },
163 // The Overlay tab's modal select committed a model.
164 AppEvent::Changed { id, value } if id == "model" => {
165 if let Some(overlay) = layers.picker.take() {
166 let label = overlay::MODELS
167 .iter()
168 .find(|(short, ..)| *short == value)
169 .map_or(value.as_str(), |(_, label, _)| label);
170 app.ui_mut().set_text("status", format!("model: {label}"));
171 app.ui_mut().close_overlay(overlay);
172 }
173 },
174 // The Picker tab's select moved: mirror the chat picker's
175 // facts-and-chips detail line.
176 AppEvent::Highlighted { id, value } if id == "models" => {
177 picker::show_detail_on(app.ui_mut(), value.as_str().parse().ok());
178 },
179 AppEvent::Filtered { id, value, .. } if id == "models" => {
180 let model = value.and_then(|value| value.as_str().parse().ok());
181 picker::show_detail_on(app.ui_mut(), model);
182 },
183 AppEvent::OverlayClosed(id) => {
184 if layers.picker == Some(id) {
185 layers.picker = None;
186 }
187 if layers.help == Some(id) {
188 layers.help = None;
189 }
190 },
191 _ => {},
192 }
193 render::sync_preview(app.ui_mut(), &mut synced);
194 // Reserve the Overlay tab's chords only while it is showing, so the
195 // focused composer can't spend Ctrl+K on kill-line — and the Live
196 // tab's editor keeps it.
197 let chords: &[Key] = if active_tab(app.ui()) == "Overlay" {
198 &[Key::Ctrl('k'), Key::Ctrl('g')]
199 } else {
200 &[]
201 };
202 app.set_hotkeys(chords.iter().copied());
203 }
204 Ok(())
205}Trait Implementations§
Auto Trait Implementations§
impl !Freeze for App
impl !RefUnwindSafe for App
impl !Send for App
impl !Sync for App
impl !UnwindSafe for App
impl Unpin for App
impl UnsafeUnpin for App
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more