pub struct Props { /* private fields */ }Expand description
Typed component attributes.
Implementations§
Source§impl Props
impl Props
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates an empty property collection.
Examples found in repository?
280 fn new(editor: Rc<RefCell<Editor>>, outcome: Rc<RefCell<Option<EditOutcome>>>) -> Self {
281 Self { props: Props::new(), slot: next_slot(), editor, outcome }
282 }
283
284 /// Cells before the editor text: the two-cell prompt plus a gap —
285 /// identical on every tier.
286 const fn input_offset() -> u16 {
287 3
288 }
289
290 /// The `╰─` composer prompt composed from the tier's round border.
291 fn input_prompt(charset: Charset) -> Str {
292 let (_, _, bl, _, horizontal, _) = charset.border(Border::Round);
293 fmts!("{bl}{horizontal}")
294 }
295
296 const fn input_width(width: u16) -> u16 {
297 width.saturating_sub(Self::input_offset()).saturating_sub(1)
298 }
299
300 fn paint_picker(pc: &mut PaintCtx<'_>, rect: Rect, y: u16, editor: &Editor) {
301 let Some(picker) = editor.picker() else {
302 return;
303 };
304 let (start, suggestions) = picker.visible_suggestions();
305 let overflow = picker.len() > suggestions.len();
306 let row_right = rect
307 .x
308 .saturating_add(rect.width.saturating_sub(u16::from(overflow)));
309 let primary_width = suggestions
310 .iter()
311 .filter_map(|suggestion| match suggestion.display() {
312 SuggestionDisplay::Text(name) => Some(visible_width(name).saturating_add(2)),
313 SuggestionDisplay::Emoji { .. } => None,
314 })
315 .max()
316 .unwrap_or(12)
317 .clamp(12, 32);
318
319 for (offset, suggestion) in suggestions.iter().enumerate() {
320 let Ok(offset) = u16::try_from(offset) else {
321 break;
322 };
323 let row = y.saturating_add(offset);
324 if row >= pc.clip {
325 break;
326 }
327 let selected = start + usize::from(offset) == picker.selected();
328 let label = if selected { ink(GREEN) } else { ink(TEXT) };
329 let description = if selected { ink(GREEN) } else { ink(MUTED) };
330 pc.frame.put(
331 rect.x,
332 row,
333 if selected {
334 pc.ctx.charset.cursor()
335 } else {
336 " "
337 },
338 label,
339 );
340 match suggestion.display() {
341 SuggestionDisplay::Text(name) => {
342 draw_line(
343 pc.frame,
344 rect.x.saturating_add(2),
345 row,
346 row_right.saturating_sub(rect.x.saturating_add(2)),
347 &[Span::new(name, label)],
348 );
349 if let Some(text) = suggestion.description()
350 && rect.width > 40
351 {
352 let description_x = rect
353 .x
354 .saturating_add(2)
355 .saturating_add(primary_width)
356 .min(row_right);
357 draw_line(
358 pc.frame,
359 description_x,
360 row,
361 row_right.saturating_sub(description_x),
362 &[Span::new(text, description)],
363 );
364 }
365 },
366 SuggestionDisplay::Emoji { emoji, shortcode } => {
367 let mut column = pc.frame.put(rect.x.saturating_add(2), row, emoji, label);
368 column = pc.frame.put(column, row, " ", label);
369 if shortcode.starts_with(':') {
370 pc.frame.put(column, row, shortcode, label);
371 } else {
372 column = pc.frame.put(column, row, ":", label);
373 column = pc.frame.put(column, row, shortcode, label);
374 pc.frame.put(column, row, ":", label);
375 }
376 },
377 }
378 }
379
380 if overflow && !suggestions.is_empty() {
381 let (track, thumb_glyph) = pc.ctx.charset.scrollbar();
382 let track_x = rect.x.saturating_add(rect.width.saturating_sub(1));
383 for offset in 0..suggestions.len() {
384 let Ok(offset) = u16::try_from(offset) else {
385 break;
386 };
387 pc.frame
388 .put(track_x, y.saturating_add(offset), track, ink(FAINT));
389 }
390 let thumb = picker
391 .selected()
392 .saturating_mul(suggestions.len().saturating_sub(1))
393 / picker.len().saturating_sub(1);
394 pc.frame.put(
395 track_x,
396 y.saturating_add(u16::try_from(thumb).unwrap_or(u16::MAX)),
397 thumb_glyph,
398 ink(GREEN),
399 );
400 }
401 }
402}
403
404impl Component for DemoInput {
405 fn props(&self) -> &Props {
406 &self.props
407 }
408
409 fn props_mut(&mut self) -> &mut Props {
410 &mut self.props
411 }
412
413 fn slot(&self) -> Slot {
414 self.slot
415 }
416
417 fn measure(&mut self, _ctx: &UiContext) -> (u16, u16) {
418 (6, 40)
419 }
420
421 fn height(&mut self, _ctx: &UiContext, width: u16) -> u16 {
422 let editor = self.editor.borrow();
423 editor
424 .input_height_for(Self::input_width(width))
425 .saturating_add(editor.picker_height())
426 }
427
428 fn paint(&mut self, pc: &mut PaintCtx<'_>, rect: Rect) {
429 pc.hits
430 .push(Hit { rect, slot: self.slot, tag: HitTag::Press });
431 let editor = self.editor.borrow();
432 let input_x = rect.x.saturating_add(Self::input_offset());
433 let input_width = Self::input_width(rect.width);
434 let input_height = editor.input_height_for(input_width);
435 let theme = Theme::default();
436 let mut in_comment = false;
437 for (offset, row) in editor.view(input_width).iter().enumerate() {
438 let row_y = rect
439 .y
440 .saturating_add(u16::try_from(offset).unwrap_or(u16::MAX));
441 if row_y >= pc.clip {
442 break;
443 }
444 if offset == 0 {
445 pc.frame
446 .put(rect.x, row_y, &Self::input_prompt(pc.ctx.charset), ink(FAINT));
447 }
448 let mut spans: SmallVec<Span<'_>, 16> = SmallVec::new();
449 if editor.options().xml {
450 let (runs, next) = highlight_xml(row.text, &theme, in_comment);
451 in_comment = next;
452 push_row_spans(&editor, row.text, &runs, &mut spans);
453 } else {
454 push_row_spans(&editor, row.text, &[], &mut spans);
455 }
456 draw_line(pc.frame, input_x, row_y, input_width, &spans);
457 if let Some(cursor_column) = row.cursor_column {
458 if cursor_column >= visible_width(row.text)
459 && let Some(hint) = editor.inline_hint()
460 {
461 let hint_x = input_x.saturating_add(cursor_column).saturating_add(1);
462 let width = input_width.saturating_sub(cursor_column.saturating_add(1));
463 draw_line(pc.frame, hint_x, row_y, width, &[Span::new(
464 hint.as_str(),
465 ink(MUTED).dim(),
466 )]);
467 }
468 pc.frame.set_cursor(
469 input_x
470 .saturating_add(cursor_column)
471 .min(rect.x.saturating_add(rect.width.saturating_sub(2))),
472 row_y,
473 );
474 }
475 }
476 Self::paint_picker(pc, rect, rect.y.saturating_add(input_height), &editor);
477 }
478
479 fn focusable(&self) -> bool {
480 true
481 }
482
483 fn key(&mut self, _ec: &mut EventCtx<'_>, key: Key) -> Flow {
484 let outcome = self.editor.borrow_mut().handle(key);
485 *self.outcome.borrow_mut() = Some(outcome);
486 // The editor owns every key while focused. In particular, an ignored
487 // picker key must not escape into `Ui`'s focus-ring navigation; the
488 // demo applies its quit policy from the recorded `EditOutcome`.
489 Flow::Consumed
490 }
491
492 fn mouse(
493 &mut self,
494 _ec: &mut EventCtx<'_>,
495 _tag: HitTag,
496 at: (u16, u16),
497 rect: Rect,
498 mouse: Mouse,
499 ) -> Flow {
500 let width = Self::input_width(rect.width);
501 match mouse {
502 Mouse::Click => {
503 self.editor.borrow_mut().set_cursor_visual_row(
504 usize::from(at.1.saturating_sub(rect.y)),
505 at.0
506 .saturating_sub(rect.x.saturating_add(Self::input_offset())),
507 width,
508 );
509 Flow::Consumed
510 },
511 Mouse::WheelUp | Mouse::WheelDown => {
512 let delta = if mouse == Mouse::WheelUp { -1 } else { 1 };
513 if self
514 .editor
515 .borrow()
516 .scroll_rows(delta, width, usize::from(rect.height))
517 {
518 Flow::Consumed
519 } else {
520 Flow::Skip
521 }
522 },
523 _ => Flow::Skip,
524 }
525 }
526
527 fn paste(&mut self, _ec: &mut EventCtx<'_>, text: &str) -> Flow {
528 if matches!(self.editor.borrow_mut().insert_text(text), EditOutcome::Changed) {
529 Flow::Consumed
530 } else {
531 Flow::Skip
532 }
533 }
534}
535/// Whether the demo is working, and how the status bar's brand segment
536/// blends between its two states.
537struct WorkState {
538 working: bool,
539 /// When the current mode began; the working timer counts from here.
540 since: Duration,
541 /// Brand foreground: [`GREEN`] while working, [`MUTED`] at rest.
542 fade: Tween<Color>,
543}
544
545/// Powerline status split into a left brand group — spinner and session
546/// timer while working, the omp brand at rest, the foreground tweening
547/// between the two so neither swap ever snaps — and a right-docked
548/// session group (branch, context, cost). Panes too narrow for both
549/// groups fall back to one left-anchored band that sheds from the tail.
550struct DemoStatus {
551 props: Props,
552 slot: Slot,
553 work: Rc<RefCell<WorkState>>,
554 model: Rc<RefCell<Str>>,
555 charset: Charset,
556 right: Status,
557}
558
559impl DemoStatus {
560 fn new(work: Rc<RefCell<WorkState>>, model: Rc<RefCell<Str>>, charset: Charset) -> Self {
561 let mut props = Props::new();
562 props.set(Prop::Id, STATUS_ID);
563 let right = Self::right_group(charset);
564 Self { props, slot: next_slot(), work, model, charset, right }
565 }Sourcepub fn with(self, prop: Prop, value: impl Into<PropValue>) -> Self
pub fn with(self, prop: Prop, value: impl Into<PropValue>) -> Self
Returns this collection with a known property assigned.
§Panics
Panics when a textual value is invalid for the selected property.
Sourcepub fn try_set(&mut self, prop: Prop, value: PropValue) -> Result<(), PropError>
pub fn try_set(&mut self, prop: Prop, value: PropValue) -> Result<(), PropError>
Validates and assigns a known property.
§Errors
Returns PropError when a textual value cannot be parsed for the
selected property.
Sourcepub fn get(&self, prop: Prop) -> Option<&PropValue>
pub fn get(&self, prop: Prop) -> Option<&PropValue>
Returns the typed value assigned to a known property.
Sourcepub fn get_str(&self, prop: Prop) -> Option<Str>
pub fn get_str(&self, prop: Prop) -> Option<Str>
Formats a known property value using its markup representation.
Sourcepub fn with_custom(
self,
name: impl Into<Str>,
value: impl Into<PropValue>,
) -> Self
pub fn with_custom( self, name: impl Into<Str>, value: impl Into<PropValue>, ) -> Self
Returns this collection with a custom property assigned.
Sourcepub fn set_custom(&mut self, name: impl Into<Str>, value: impl Into<PropValue>)
pub fn set_custom(&mut self, name: impl Into<Str>, value: impl Into<PropValue>)
Assigns or replaces a custom property.
Sourcepub fn custom(&self, name: &str) -> Option<&PropValue>
pub fn custom(&self, name: &str) -> Option<&PropValue>
Returns a custom property by its literal name.
Sourcepub fn named(&self, name: &str) -> Option<&PropValue>
pub fn named(&self, name: &str) -> Option<&PropValue>
Returns either a known or custom property by markup name.
Sourcepub fn prop_of(name: &str) -> Option<Prop>
pub fn prop_of(name: &str) -> Option<Prop>
Resolves a markup attribute name to its well-known property — the
kebab-cased variant ident derived on Prop.
Sourcepub fn grow(&self) -> Option<f32>
pub fn grow(&self) -> Option<f32>
Returns the flexible growth weight, with a bare flag meaning one.
Sourcepub fn truncate(&self) -> Option<Truncate>
pub fn truncate(&self) -> Option<Truncate>
Returns the truncation mode: a bare truncate flag clips the end,
truncate=start clips the beginning; None disables truncation.
Sourcepub fn wrap_chars(&self) -> bool
pub fn wrap_chars(&self) -> bool
Whether text flows grapheme-exact to the width (wrap=char) like a
bare terminal: every break is a byte-preserving soft wrap the
renderer re-joins for native copy. Defaults to word wrapping.
Sourcepub fn guides(&self) -> Option<Border>
pub fn guides(&self) -> Option<Border>
Returns the tree guide connector family; a bare flag means square.
Sourcepub fn title_align(&self) -> Align
pub fn title_align(&self) -> Align
Returns the border-title placement, defaulting to the start edge.
Returns the border-footer placement, defaulting to the start edge.
Returns the footer shown on a framed container’s bottom border.
Sourcepub fn anim(&self) -> Option<Duration>
pub fn anim(&self) -> Option<Duration>
Transition duration for animatable properties, when anim is set.
A bare anim flag selects 200ms.
Sourcepub fn ease(&self) -> Easing
pub fn ease(&self) -> Easing
Easing curve for anim transitions, defaulting to ease-out — the
natural shape for state changes that should land softly.
Sourcepub fn spin(&self) -> Option<Duration>
pub fn spin(&self) -> Option<Duration>
Gradient rotation period, when spin is set. A bare spin flag
selects one revolution every 3 seconds.
Sourcepub fn shimmer(&self) -> Option<Duration>
pub fn shimmer(&self) -> Option<Duration>
Brightness-crest sweep period, when shimmer is set. A bare
shimmer flag selects one sweep every 2 seconds.
Sourcepub fn reveal(&self) -> Option<Duration>
pub fn reveal(&self) -> Option<Duration>
Streamed-text reveal catch-up horizon, when reveal is set. A bare
reveal flag selects 250ms; reveal=0 shows new text immediately.
Sourcepub fn str_of(&self, prop: Prop) -> Option<&Str>
pub fn str_of(&self, prop: Prop) -> Option<&Str>
Returns the textual payload of a property.
Examples found in repository?
107fn build_ui(viewport: Size, context: UiContext) -> Ui {
108 let ids = PROVIDERS
109 .iter()
110 .enumerate()
111 .map(|(index, provider)| {
112 (format!("{ASSET_DIR}/{}.png", provider.id), u32::try_from(index + 1).unwrap())
113 })
114 .collect::<HashMap<_, _>>();
115 let elements = Elements::builder()
116 .with("logo", move |_: &str, props: Props, _: Vec<Cached>| {
117 let source = props.str_of(Prop::Src).map_or("", |value| value.as_str());
118 let id = ids.get(source).copied().unwrap_or(1);
119 Box::new(
120 Img::new()
121 .with_str(Prop::Src, source)
122 .with(Prop::W, 4_u16)
123 .kitty(id, 2, 4),
124 ) as Box<dyn omp_tui::Component>
125 })
126 .build();
127 let root = dom! {
128 <col gap=1>
129 <row gap=1>
130 <i:log-in/>
131 <text bold fg="accent..info">{"Choose a provider"}</text>
132 <text dim>{format!("{} providers", PROVIDERS.len())}</text>
133 </row>
134 <scroll id={SCROLL_ID} h={scroll_height(viewport)}>
135 <row wrap gap=1 justify=center>
136 for provider in PROVIDERS.iter() {
137 <box focus id={provider.id} w={CARD_W} border=round bc="muted..muted"
138 hover="#38bdf8..#c084fc" lift=1 anim=220 ease=in-out
139 align=center pad-x=1>
140 <logo src={format!("{ASSET_DIR}/{}.png", provider.id)}/>
141 <text bold truncate align=center>{provider.name}</text>
142 </box>
143 }
144 </row>
145 </scroll>
146 <row gap=2>
147 <text dim>{"↹/←→/↑↓ pick · ↵ login · wheel scroll · Ctrl-C quit"}</text>
148 <text id={HUD_ID} dim>{"repaint: 0 cells"}</text>
149 </row>
150 </col>
151 };
152 let context = UiContext { elements, ..context };
153 Ui::from_root(root, viewport.width, context)
154}Trait Implementations§
Auto Trait Implementations§
impl Freeze for Props
impl RefUnwindSafe for Props
impl Send for Props
impl Sync for Props
impl Unpin for Props
impl UnsafeUnpin for Props
impl UnwindSafe for Props
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> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
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> DowncastSync for T
impl<T> DowncastSync for T
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