Skip to main content

Style

Struct Style 

Source
pub struct Style { /* private fields */ }
Expand description

Canonical visual attributes for one or more cells.

Implementations§

Source§

impl Style

Source

pub const fn new() -> Self

Creates an unstyled terminal style.

Examples found in repository?
examples/footers.rs (line 399)
398const fn ink(color: Color) -> Style {
399	Style::new().fg(color)
400}
401
402fn width_of(text: &str) -> u16 {
403	u16::try_from(xutf::width_str(text)).unwrap_or(u16::MAX)
404}
405
406/// [`TITLE`] truncated to at most `max` cells, ellipsized when it cannot
407/// fit whole.
408fn fit_title(scene: &Scene, max: u16) -> Str {
409	if width_of(TITLE) <= max {
410		return Str::new_static(TITLE);
411	}
412	let ellipsis = match scene.charset {
413		Charset::Ascii => "...",
414		_ => "…",
415	};
416	let budget = max.saturating_sub(width_of(ellipsis));
417	let mut used = 0_u16;
418	let mut end = 0_usize;
419	for grapheme in xutf::graphemes_str(TITLE) {
420		let cells = width_of(grapheme);
421		if used.saturating_add(cells) > budget {
422			break;
423		}
424		used = used.saturating_add(cells);
425		end += grapheme.len();
426	}
427	if end == 0 {
428		return Str::default();
429	}
430	fmts!("{}{ellipsis}", TITLE[..end].trim_end())
431}
432
433/// Total cells a powerline band with `segments` occupies, mirroring the
434/// `<status>` component's measurement.
435fn band_width(scene: &Scene, segments: &[Seg]) -> u16 {
436	let (left_cap, separator, right_cap) = band_chrome(scene.charset);
437	let text = segments
438		.iter()
439		.map(|segment| width_of(&segment.label))
440		.fold(0_u16, u16::saturating_add);
441	let separators = u16::try_from(segments.len().saturating_sub(1))
442		.unwrap_or(u16::MAX)
443		.saturating_mul(width_of(separator).saturating_add(2));
444	text
445		.saturating_add(separators)
446		.saturating_add(width_of(left_cap))
447		.saturating_add(2)
448		.saturating_add(width_of(right_cap))
449}
450
451/// Paints a powerline band at `x`: cap, padded segments, cap.
452fn draw_band(frame: &mut Frame, x: u16, y: u16, scene: &Scene, segments: &[Seg]) {
453	let (left_cap, separator, right_cap) = band_chrome(scene.charset);
454	let base = Style::new().fg(TEXT).bg(BAND_BG);
455	let edge = ink(BAND_BG);
456	let mut column = frame.put(x, y, left_cap, edge);
457	column = frame.put(column, y, " ", base);
458	for (index, segment) in segments.iter().enumerate() {
459		if index > 0 {
460			column = frame.put(column, y, " ", base.dim());
461			column = frame.put(column, y, separator, base.dim());
462			column = frame.put(column, y, " ", base.dim());
463		}
464		column = frame.put(column, y, &segment.label, base.fg(segment.color));
465	}
466	column = frame.put(column, y, " ", base);
467	frame.put(column, y, right_cap, edge);
468}
More examples
Hide additional examples
examples/chat/demo.rs (line 1868)
1847fn draw_submission_text(
1848	frame: &mut Frame,
1849	x: u16,
1850	y: u16,
1851	width: u16,
1852	line: &str,
1853	charset: Charset,
1854) {
1855	let icon = charset.icon(Icon::Image);
1856	let mut chips: SmallVec<(usize, usize, String, usize), 4> = SmallVec::new();
1857	let mut base = 0;
1858	while let Some((start, end, marker)) = next_ref_tag(&line[base..]) {
1859		chips.push((base + start, base + end, format!("{icon} #{marker}"), marker));
1860		base += end;
1861	}
1862	let mut spans: SmallVec<Span<'_>, 8> = SmallVec::new();
1863	let mut at = 0;
1864	for (start, end, label, marker) in &chips {
1865		if *start > at {
1866			spans.push(Span::new(&line[at..*start], ink(TEXT)));
1867		}
1868		spans.push(Span::new(label, Style::new().fg(PANEL).bg(attachment_color(*marker)).bold()));
1869		at = *end;
1870	}
1871	if at < line.len() {
1872		spans.push(Span::new(&line[at..], ink(TEXT)));
1873	}
1874	draw_line(frame, x, y, width, &spans);
1875}
1876
1877/// Chrome outside a panel is transparent: no `bg`, so the terminal's own
1878/// background (and any image or blur behind it) shows through. Only the
1879/// panel boxes below opt into a fill.
1880const fn base_style() -> Style {
1881	Style::new().fg(TEXT)
1882}
1883
1884const fn panel_style() -> Style {
1885	Style::new().fg(TEXT).bg(PANEL)
1886}
1887
1888const fn ink(color: Color) -> Style {
1889	Style::new().fg(color)
1890}
1891
1892const fn panel_ink(color: Color) -> Style {
1893	Style::new().fg(color).bg(PANEL)
1894}
1895
1896const fn prose_style() -> Style {
1897	Style::new().fg(MUTED).italic()
1898}
1899
1900const fn code_style() -> Style {
1901	Style::new().fg(GREEN)
1902}
examples/chat/welcome.rs (line 276)
257	fn draw_backdrop(&mut self, viewport: Size, clock: Duration, elapsed: f32) {
258		let interval = ambient_interval(clock, BACKDROP_IDLE_INTERVAL);
259		if self
260			.backdrop_at
261			.is_none_or(|rendered_at| clock.saturating_sub(rendered_at) >= interval)
262		{
263			let fade = smooth((elapsed / BACKDROP_FADE).clamp(0.0, 1.0));
264			self
265				.backdrop_frame
266				.fill(Rect::new(0, 0, viewport.width, viewport.height), Style::default());
267			let frame = &mut self.backdrop_frame;
268			let mut buffer = [0_u8; 4];
269			let dim = |color: Color| Color::Rgb(0, 0, 0).lerp(color, fade);
270			self.surface.render(
271				&mut self.backdrop,
272				clock,
273				viewport.width,
274				viewport.height,
275				|x, y, glyph, fg, bg| {
276					let style = Style::new().fg(dim(fg));
277					let style = match bg {
278						Some(bg) => style.bg(dim(bg)),
279						None => style,
280					};
281					frame.put(x, y, glyph.encode_utf8(&mut buffer), style);
282				},
283			);
284			self.backdrop_at = Some(clock);
285		}
286		self.frame.clone_from(&self.backdrop_frame);
287	}
288
289	fn draw_card(&mut self, cols: u16, left: u16, top: u16, elapsed: f32, hover: f32) {
290		let full = cols == CARD_COLS;
291		let logo_left = if full {
292			left + 3
293		} else {
294			left + (cols - LOGO_COLS as u16) / 2
295		};
296		self.logo_origin = (logo_left, top + 2);
297		// Pointer-tracking border glow: the brand gradient sampled by angle
298		// around the card center (the disk's own palette), strongest near
299		// the pointer, scaled by the eased hover amount.
300		let pointer = self.pointer;
301		let center =
302			(f32::from(left) + f32::from(cols) / 2.0, f32::from(top) + f32::from(CARD_ROWS) / 2.0);
303		let edge_at = move |x: u16, y: u16| -> Style {
304			let Some((px, py)) = pointer.filter(|_| hover > 0.02) else {
305				return on_card(CARD_BORDER);
306			};
307			let dx = (f32::from(x) - f32::from(px)) * 0.5;
308			let dy = f32::from(y) - f32::from(py);
309			let glow = hover * (-(dx * dx + dy * dy) / 34.0).exp();
310			if glow < 0.02 {
311				return on_card(CARD_BORDER);
312			}
313			let angle = (f32::from(y) - center.1).atan2((f32::from(x) - center.0) * 0.5);
314			let brand = vec3_color(gradient(angle - elapsed * 0.5));
315			on_card(CARD_BORDER.lerp(brand, glow))
316		};
317		let frame = &mut self.frame;
318		frame.fill(Rect::new(left, top, cols, CARD_ROWS), on_card(TEXT));
319
320		let right = left + cols - 1;
321		let bottom = top + CARD_ROWS - 1;
322		let divider = bottom - 2;
323		let (tl, tr, bl, br, horizontal, vertical) = self.charset.border(Border::Round);
324		let grid = self.charset.grid();
325		let mut glyph = [0_u8; 4];
326		frame.put(left, top, tl.encode_utf8(&mut glyph), edge_at(left, top));
327		frame.put(right, top, tr.encode_utf8(&mut glyph), edge_at(right, top));
328		frame.put(left, divider, grid.middle.0.encode_utf8(&mut glyph), edge_at(left, divider));
329		frame.put(right, divider, grid.middle.2.encode_utf8(&mut glyph), edge_at(right, divider));
330		frame.put(left, bottom, bl.encode_utf8(&mut glyph), edge_at(left, bottom));
331		frame.put(right, bottom, br.encode_utf8(&mut glyph), edge_at(right, bottom));
332		for x in left + 1..right {
333			frame.put(x, top, horizontal.encode_utf8(&mut glyph), edge_at(x, top));
334			frame.put(x, divider, horizontal.encode_utf8(&mut glyph), edge_at(x, divider));
335			frame.put(x, bottom, horizontal.encode_utf8(&mut glyph), edge_at(x, bottom));
336		}
337		for y in top + 1..bottom {
338			if y != divider {
339				frame.put(left, y, vertical.encode_utf8(&mut glyph), edge_at(left, y));
340				frame.put(right, y, vertical.encode_utf8(&mut glyph), edge_at(right, y));
341			}
342		}
343
344		frame.put(left + 2, top, self.title.as_str(), on_card(TEXT_STRONG));
345		if full {
346			frame.put(left + 34, top, " SESSION ORBIT ", on_card(FAINT));
347			draw_dust(frame, left, top, elapsed);
348			draw_sessions(frame, left, top, self.charset);
349			draw_beam(frame, left, top, elapsed);
350		}
351
352		blit_logo(frame, &self.logo, logo_left, top + 2, CARD_BG);
353
354		let footer = divider + 1;
355		frame.fill(Rect::new(left + 1, footer, cols - 2, 1), on_footer(TEXT));
356		if full {
357			frame.put(left + 3, divider, " SHORTCUTS ", on_card(FAINT));
358			let dot = fmts!(" {} ", self.charset.icon(Icon::Enabled));
359			let x = frame.put(left + cols - 21, top, &dot, on_card(GREEN));
360			frame.put(x, top, "rust-analyzer ", on_card(MUTED));
361			draw_full_hints(frame, left, footer);
362		} else {
363			draw_smol_hints(frame, left, cols, footer);
364		}
365	}
366}
367
368impl Default for Welcome {
369	fn default() -> Self {
370		Self::new(Charset::NerdFont)
371	}
372}
373
374fn draw_dust(frame: &mut Frame, left: u16, top: u16, elapsed: f32) {
375	for &(x, y, offset) in &DUST {
376		let pulse = 0.5 + 0.5 * (elapsed * 1.4 + offset).sin();
377		let color = FAINT.lerp(CYAN, pulse * 0.28);
378		frame.put(left + x, top + y, "·", on_card(color));
379	}
380	frame.put(left + 1, top + 7, HORIZON, on_card(FAINT.lerp(INDIGO, 0.16)));
381	frame.put(left + 14, top + 1, "+Z", on_card(FAINT));
382}
383
384fn draw_beam(frame: &mut Frame, left: u16, top: u16, elapsed: f32) {
385	let phase = (elapsed * 9.0) as usize % BEAM.len();
386	for (index, &(x, y, glyph)) in BEAM.iter().enumerate() {
387		let direct = index.abs_diff(phase);
388		let distance = direct.min(BEAM.len() - direct);
389		let color = match distance {
390			0 => TEXT_STRONG,
391			1 => CYAN,
392			_ => FAINT.lerp(INDIGO, 0.34),
393		};
394		frame.put(left + x, top + y, glyph, on_card(color));
395	}
396}
397
398fn draw_sessions(frame: &mut Frame, left: u16, top: u16, charset: Charset) {
399	let (_, _, _, _, _, vertical) = charset.border(Border::Round);
400	let mut glyph = [0_u8; 4];
401	let panel_x = left + 36;
402	frame.put(panel_x, top + 2, "RECENT SESSIONS", on_card(MUTED));
403	frame.put(left + CARD_COLS - 14, top + 2, "4 / LOCAL", on_card(FAINT));
404	for y in top + 4..=top + 10 {
405		frame.put(panel_x, y, vertical.encode_utf8(&mut glyph), on_card(FAINT.lerp(INDIGO, 0.18)));
406	}
407	for (index, (label, age)) in SESSIONS.iter().enumerate() {
408		let y = top + 4 + index as u16 * 2;
409		if index == 0 {
410			frame.fill(Rect::new(panel_x - 2, y, CARD_COLS - 35, 1), on_selected(TEXT));
411			frame.put(panel_x - 2, y, charset.rail(), on_selected(GREEN));
412			frame.put(panel_x, y, charset.radio(true), on_selected(GREEN));
413			frame.put(panel_x + 2, y, age, on_selected(GREEN));
414			frame.put(panel_x + 7, y, label, on_selected(TEXT_STRONG));
415		} else {
416			frame.put(panel_x, y, charset.radio(false), on_card(FAINT));
417			frame.put(panel_x + 2, y, age, on_card(FAINT));
418			frame.put(panel_x + 7, y, label, on_card(MUTED));
419		}
420	}
421}
422
423fn draw_full_hints(frame: &mut Frame, left: u16, y: u16) {
424	frame.put(left + 3, y, "#", on_footer(CYAN));
425	frame.put(left + 5, y, "actions", on_footer(MUTED));
426	frame.put(left + 14, y, "/", on_footer(GREEN));
427	frame.put(left + 16, y, "commands", on_footer(MUTED));
428	frame.put(left + 27, y, "!", on_footer(AMBER));
429	frame.put(left + 29, y, "shell", on_footer(MUTED));
430	frame.put(left + 37, y, "$", on_footer(VIOLET));
431	frame.put(left + 39, y, "python", on_footer(MUTED));
432	frame.put(left + CARD_COLS - 23, y, "↑↓ move", on_footer(FAINT));
433	frame.put(left + CARD_COLS - 13, y, "↵ resume", on_footer(TEXT_STRONG));
434}
435
436fn draw_smol_hints(frame: &mut Frame, left: u16, cols: u16, y: u16) {
437	frame.put(left + 3, y, "#", on_footer(CYAN).bold());
438	frame.put(left + 5, y, "/", on_footer(CYAN).bold());
439	frame.put(left + 7, y, "!", on_footer(AMBER).bold());
440	frame.put(left + 9, y, "$", on_footer(GREEN).bold());
441	frame.put(left + cols - 14, y, "enter", on_footer(FAINT));
442	frame.put(left + cols - 8, y, "resume", on_footer(TEXT_STRONG).bold());
443}
444
445fn blit_logo(frame: &mut Frame, logo: &LogoGrid, left: u16, top: u16, background: Color) {
446	let mut buffer = [0_u8; 4];
447	for (row, cells) in logo.iter().enumerate() {
448		for (column, cell) in cells.iter().enumerate() {
449			let Some((glyph, color)) = cell else { continue };
450			let style = Style::new().fg(*color).bg(background);
451			frame.put(left + column as u16, top + row as u16, glyph.encode_utf8(&mut buffer), style);
452		}
453	}
454}
455
456const fn on_card(fg: Color) -> Style {
457	Style::new().fg(fg).bg(CARD_BG)
458}
459
460const fn on_footer(fg: Color) -> Style {
461	Style::new().fg(fg).bg(FOOTER_BG)
462}
463
464const fn on_selected(fg: Color) -> Style {
465	Style::new().fg(fg).bg(SELECTED_BG)
466}
Source

pub const fn fg(self, color: Color) -> Self

Sets the foreground color.

Examples found in repository?
examples/footers.rs (line 399)
398const fn ink(color: Color) -> Style {
399	Style::new().fg(color)
400}
401
402fn width_of(text: &str) -> u16 {
403	u16::try_from(xutf::width_str(text)).unwrap_or(u16::MAX)
404}
405
406/// [`TITLE`] truncated to at most `max` cells, ellipsized when it cannot
407/// fit whole.
408fn fit_title(scene: &Scene, max: u16) -> Str {
409	if width_of(TITLE) <= max {
410		return Str::new_static(TITLE);
411	}
412	let ellipsis = match scene.charset {
413		Charset::Ascii => "...",
414		_ => "…",
415	};
416	let budget = max.saturating_sub(width_of(ellipsis));
417	let mut used = 0_u16;
418	let mut end = 0_usize;
419	for grapheme in xutf::graphemes_str(TITLE) {
420		let cells = width_of(grapheme);
421		if used.saturating_add(cells) > budget {
422			break;
423		}
424		used = used.saturating_add(cells);
425		end += grapheme.len();
426	}
427	if end == 0 {
428		return Str::default();
429	}
430	fmts!("{}{ellipsis}", TITLE[..end].trim_end())
431}
432
433/// Total cells a powerline band with `segments` occupies, mirroring the
434/// `<status>` component's measurement.
435fn band_width(scene: &Scene, segments: &[Seg]) -> u16 {
436	let (left_cap, separator, right_cap) = band_chrome(scene.charset);
437	let text = segments
438		.iter()
439		.map(|segment| width_of(&segment.label))
440		.fold(0_u16, u16::saturating_add);
441	let separators = u16::try_from(segments.len().saturating_sub(1))
442		.unwrap_or(u16::MAX)
443		.saturating_mul(width_of(separator).saturating_add(2));
444	text
445		.saturating_add(separators)
446		.saturating_add(width_of(left_cap))
447		.saturating_add(2)
448		.saturating_add(width_of(right_cap))
449}
450
451/// Paints a powerline band at `x`: cap, padded segments, cap.
452fn draw_band(frame: &mut Frame, x: u16, y: u16, scene: &Scene, segments: &[Seg]) {
453	let (left_cap, separator, right_cap) = band_chrome(scene.charset);
454	let base = Style::new().fg(TEXT).bg(BAND_BG);
455	let edge = ink(BAND_BG);
456	let mut column = frame.put(x, y, left_cap, edge);
457	column = frame.put(column, y, " ", base);
458	for (index, segment) in segments.iter().enumerate() {
459		if index > 0 {
460			column = frame.put(column, y, " ", base.dim());
461			column = frame.put(column, y, separator, base.dim());
462			column = frame.put(column, y, " ", base.dim());
463		}
464		column = frame.put(column, y, &segment.label, base.fg(segment.color));
465	}
466	column = frame.put(column, y, " ", base);
467	frame.put(column, y, right_cap, edge);
468}
More examples
Hide additional examples
examples/chat/demo.rs (line 1868)
1847fn draw_submission_text(
1848	frame: &mut Frame,
1849	x: u16,
1850	y: u16,
1851	width: u16,
1852	line: &str,
1853	charset: Charset,
1854) {
1855	let icon = charset.icon(Icon::Image);
1856	let mut chips: SmallVec<(usize, usize, String, usize), 4> = SmallVec::new();
1857	let mut base = 0;
1858	while let Some((start, end, marker)) = next_ref_tag(&line[base..]) {
1859		chips.push((base + start, base + end, format!("{icon} #{marker}"), marker));
1860		base += end;
1861	}
1862	let mut spans: SmallVec<Span<'_>, 8> = SmallVec::new();
1863	let mut at = 0;
1864	for (start, end, label, marker) in &chips {
1865		if *start > at {
1866			spans.push(Span::new(&line[at..*start], ink(TEXT)));
1867		}
1868		spans.push(Span::new(label, Style::new().fg(PANEL).bg(attachment_color(*marker)).bold()));
1869		at = *end;
1870	}
1871	if at < line.len() {
1872		spans.push(Span::new(&line[at..], ink(TEXT)));
1873	}
1874	draw_line(frame, x, y, width, &spans);
1875}
1876
1877/// Chrome outside a panel is transparent: no `bg`, so the terminal's own
1878/// background (and any image or blur behind it) shows through. Only the
1879/// panel boxes below opt into a fill.
1880const fn base_style() -> Style {
1881	Style::new().fg(TEXT)
1882}
1883
1884const fn panel_style() -> Style {
1885	Style::new().fg(TEXT).bg(PANEL)
1886}
1887
1888const fn ink(color: Color) -> Style {
1889	Style::new().fg(color)
1890}
1891
1892const fn panel_ink(color: Color) -> Style {
1893	Style::new().fg(color).bg(PANEL)
1894}
1895
1896const fn prose_style() -> Style {
1897	Style::new().fg(MUTED).italic()
1898}
1899
1900const fn code_style() -> Style {
1901	Style::new().fg(GREEN)
1902}
examples/chat/welcome.rs (line 276)
257	fn draw_backdrop(&mut self, viewport: Size, clock: Duration, elapsed: f32) {
258		let interval = ambient_interval(clock, BACKDROP_IDLE_INTERVAL);
259		if self
260			.backdrop_at
261			.is_none_or(|rendered_at| clock.saturating_sub(rendered_at) >= interval)
262		{
263			let fade = smooth((elapsed / BACKDROP_FADE).clamp(0.0, 1.0));
264			self
265				.backdrop_frame
266				.fill(Rect::new(0, 0, viewport.width, viewport.height), Style::default());
267			let frame = &mut self.backdrop_frame;
268			let mut buffer = [0_u8; 4];
269			let dim = |color: Color| Color::Rgb(0, 0, 0).lerp(color, fade);
270			self.surface.render(
271				&mut self.backdrop,
272				clock,
273				viewport.width,
274				viewport.height,
275				|x, y, glyph, fg, bg| {
276					let style = Style::new().fg(dim(fg));
277					let style = match bg {
278						Some(bg) => style.bg(dim(bg)),
279						None => style,
280					};
281					frame.put(x, y, glyph.encode_utf8(&mut buffer), style);
282				},
283			);
284			self.backdrop_at = Some(clock);
285		}
286		self.frame.clone_from(&self.backdrop_frame);
287	}
288
289	fn draw_card(&mut self, cols: u16, left: u16, top: u16, elapsed: f32, hover: f32) {
290		let full = cols == CARD_COLS;
291		let logo_left = if full {
292			left + 3
293		} else {
294			left + (cols - LOGO_COLS as u16) / 2
295		};
296		self.logo_origin = (logo_left, top + 2);
297		// Pointer-tracking border glow: the brand gradient sampled by angle
298		// around the card center (the disk's own palette), strongest near
299		// the pointer, scaled by the eased hover amount.
300		let pointer = self.pointer;
301		let center =
302			(f32::from(left) + f32::from(cols) / 2.0, f32::from(top) + f32::from(CARD_ROWS) / 2.0);
303		let edge_at = move |x: u16, y: u16| -> Style {
304			let Some((px, py)) = pointer.filter(|_| hover > 0.02) else {
305				return on_card(CARD_BORDER);
306			};
307			let dx = (f32::from(x) - f32::from(px)) * 0.5;
308			let dy = f32::from(y) - f32::from(py);
309			let glow = hover * (-(dx * dx + dy * dy) / 34.0).exp();
310			if glow < 0.02 {
311				return on_card(CARD_BORDER);
312			}
313			let angle = (f32::from(y) - center.1).atan2((f32::from(x) - center.0) * 0.5);
314			let brand = vec3_color(gradient(angle - elapsed * 0.5));
315			on_card(CARD_BORDER.lerp(brand, glow))
316		};
317		let frame = &mut self.frame;
318		frame.fill(Rect::new(left, top, cols, CARD_ROWS), on_card(TEXT));
319
320		let right = left + cols - 1;
321		let bottom = top + CARD_ROWS - 1;
322		let divider = bottom - 2;
323		let (tl, tr, bl, br, horizontal, vertical) = self.charset.border(Border::Round);
324		let grid = self.charset.grid();
325		let mut glyph = [0_u8; 4];
326		frame.put(left, top, tl.encode_utf8(&mut glyph), edge_at(left, top));
327		frame.put(right, top, tr.encode_utf8(&mut glyph), edge_at(right, top));
328		frame.put(left, divider, grid.middle.0.encode_utf8(&mut glyph), edge_at(left, divider));
329		frame.put(right, divider, grid.middle.2.encode_utf8(&mut glyph), edge_at(right, divider));
330		frame.put(left, bottom, bl.encode_utf8(&mut glyph), edge_at(left, bottom));
331		frame.put(right, bottom, br.encode_utf8(&mut glyph), edge_at(right, bottom));
332		for x in left + 1..right {
333			frame.put(x, top, horizontal.encode_utf8(&mut glyph), edge_at(x, top));
334			frame.put(x, divider, horizontal.encode_utf8(&mut glyph), edge_at(x, divider));
335			frame.put(x, bottom, horizontal.encode_utf8(&mut glyph), edge_at(x, bottom));
336		}
337		for y in top + 1..bottom {
338			if y != divider {
339				frame.put(left, y, vertical.encode_utf8(&mut glyph), edge_at(left, y));
340				frame.put(right, y, vertical.encode_utf8(&mut glyph), edge_at(right, y));
341			}
342		}
343
344		frame.put(left + 2, top, self.title.as_str(), on_card(TEXT_STRONG));
345		if full {
346			frame.put(left + 34, top, " SESSION ORBIT ", on_card(FAINT));
347			draw_dust(frame, left, top, elapsed);
348			draw_sessions(frame, left, top, self.charset);
349			draw_beam(frame, left, top, elapsed);
350		}
351
352		blit_logo(frame, &self.logo, logo_left, top + 2, CARD_BG);
353
354		let footer = divider + 1;
355		frame.fill(Rect::new(left + 1, footer, cols - 2, 1), on_footer(TEXT));
356		if full {
357			frame.put(left + 3, divider, " SHORTCUTS ", on_card(FAINT));
358			let dot = fmts!(" {} ", self.charset.icon(Icon::Enabled));
359			let x = frame.put(left + cols - 21, top, &dot, on_card(GREEN));
360			frame.put(x, top, "rust-analyzer ", on_card(MUTED));
361			draw_full_hints(frame, left, footer);
362		} else {
363			draw_smol_hints(frame, left, cols, footer);
364		}
365	}
366}
367
368impl Default for Welcome {
369	fn default() -> Self {
370		Self::new(Charset::NerdFont)
371	}
372}
373
374fn draw_dust(frame: &mut Frame, left: u16, top: u16, elapsed: f32) {
375	for &(x, y, offset) in &DUST {
376		let pulse = 0.5 + 0.5 * (elapsed * 1.4 + offset).sin();
377		let color = FAINT.lerp(CYAN, pulse * 0.28);
378		frame.put(left + x, top + y, "·", on_card(color));
379	}
380	frame.put(left + 1, top + 7, HORIZON, on_card(FAINT.lerp(INDIGO, 0.16)));
381	frame.put(left + 14, top + 1, "+Z", on_card(FAINT));
382}
383
384fn draw_beam(frame: &mut Frame, left: u16, top: u16, elapsed: f32) {
385	let phase = (elapsed * 9.0) as usize % BEAM.len();
386	for (index, &(x, y, glyph)) in BEAM.iter().enumerate() {
387		let direct = index.abs_diff(phase);
388		let distance = direct.min(BEAM.len() - direct);
389		let color = match distance {
390			0 => TEXT_STRONG,
391			1 => CYAN,
392			_ => FAINT.lerp(INDIGO, 0.34),
393		};
394		frame.put(left + x, top + y, glyph, on_card(color));
395	}
396}
397
398fn draw_sessions(frame: &mut Frame, left: u16, top: u16, charset: Charset) {
399	let (_, _, _, _, _, vertical) = charset.border(Border::Round);
400	let mut glyph = [0_u8; 4];
401	let panel_x = left + 36;
402	frame.put(panel_x, top + 2, "RECENT SESSIONS", on_card(MUTED));
403	frame.put(left + CARD_COLS - 14, top + 2, "4 / LOCAL", on_card(FAINT));
404	for y in top + 4..=top + 10 {
405		frame.put(panel_x, y, vertical.encode_utf8(&mut glyph), on_card(FAINT.lerp(INDIGO, 0.18)));
406	}
407	for (index, (label, age)) in SESSIONS.iter().enumerate() {
408		let y = top + 4 + index as u16 * 2;
409		if index == 0 {
410			frame.fill(Rect::new(panel_x - 2, y, CARD_COLS - 35, 1), on_selected(TEXT));
411			frame.put(panel_x - 2, y, charset.rail(), on_selected(GREEN));
412			frame.put(panel_x, y, charset.radio(true), on_selected(GREEN));
413			frame.put(panel_x + 2, y, age, on_selected(GREEN));
414			frame.put(panel_x + 7, y, label, on_selected(TEXT_STRONG));
415		} else {
416			frame.put(panel_x, y, charset.radio(false), on_card(FAINT));
417			frame.put(panel_x + 2, y, age, on_card(FAINT));
418			frame.put(panel_x + 7, y, label, on_card(MUTED));
419		}
420	}
421}
422
423fn draw_full_hints(frame: &mut Frame, left: u16, y: u16) {
424	frame.put(left + 3, y, "#", on_footer(CYAN));
425	frame.put(left + 5, y, "actions", on_footer(MUTED));
426	frame.put(left + 14, y, "/", on_footer(GREEN));
427	frame.put(left + 16, y, "commands", on_footer(MUTED));
428	frame.put(left + 27, y, "!", on_footer(AMBER));
429	frame.put(left + 29, y, "shell", on_footer(MUTED));
430	frame.put(left + 37, y, "$", on_footer(VIOLET));
431	frame.put(left + 39, y, "python", on_footer(MUTED));
432	frame.put(left + CARD_COLS - 23, y, "↑↓ move", on_footer(FAINT));
433	frame.put(left + CARD_COLS - 13, y, "↵ resume", on_footer(TEXT_STRONG));
434}
435
436fn draw_smol_hints(frame: &mut Frame, left: u16, cols: u16, y: u16) {
437	frame.put(left + 3, y, "#", on_footer(CYAN).bold());
438	frame.put(left + 5, y, "/", on_footer(CYAN).bold());
439	frame.put(left + 7, y, "!", on_footer(AMBER).bold());
440	frame.put(left + 9, y, "$", on_footer(GREEN).bold());
441	frame.put(left + cols - 14, y, "enter", on_footer(FAINT));
442	frame.put(left + cols - 8, y, "resume", on_footer(TEXT_STRONG).bold());
443}
444
445fn blit_logo(frame: &mut Frame, logo: &LogoGrid, left: u16, top: u16, background: Color) {
446	let mut buffer = [0_u8; 4];
447	for (row, cells) in logo.iter().enumerate() {
448		for (column, cell) in cells.iter().enumerate() {
449			let Some((glyph, color)) = cell else { continue };
450			let style = Style::new().fg(*color).bg(background);
451			frame.put(left + column as u16, top + row as u16, glyph.encode_utf8(&mut buffer), style);
452		}
453	}
454}
455
456const fn on_card(fg: Color) -> Style {
457	Style::new().fg(fg).bg(CARD_BG)
458}
459
460const fn on_footer(fg: Color) -> Style {
461	Style::new().fg(fg).bg(FOOTER_BG)
462}
463
464const fn on_selected(fg: Color) -> Style {
465	Style::new().fg(fg).bg(SELECTED_BG)
466}
Source

pub const fn bg(self, color: Color) -> Self

Sets the background color.

Examples found in repository?
examples/footers.rs (line 454)
452fn draw_band(frame: &mut Frame, x: u16, y: u16, scene: &Scene, segments: &[Seg]) {
453	let (left_cap, separator, right_cap) = band_chrome(scene.charset);
454	let base = Style::new().fg(TEXT).bg(BAND_BG);
455	let edge = ink(BAND_BG);
456	let mut column = frame.put(x, y, left_cap, edge);
457	column = frame.put(column, y, " ", base);
458	for (index, segment) in segments.iter().enumerate() {
459		if index > 0 {
460			column = frame.put(column, y, " ", base.dim());
461			column = frame.put(column, y, separator, base.dim());
462			column = frame.put(column, y, " ", base.dim());
463		}
464		column = frame.put(column, y, &segment.label, base.fg(segment.color));
465	}
466	column = frame.put(column, y, " ", base);
467	frame.put(column, y, right_cap, edge);
468}
More examples
Hide additional examples
examples/chat/demo.rs (line 1868)
1847fn draw_submission_text(
1848	frame: &mut Frame,
1849	x: u16,
1850	y: u16,
1851	width: u16,
1852	line: &str,
1853	charset: Charset,
1854) {
1855	let icon = charset.icon(Icon::Image);
1856	let mut chips: SmallVec<(usize, usize, String, usize), 4> = SmallVec::new();
1857	let mut base = 0;
1858	while let Some((start, end, marker)) = next_ref_tag(&line[base..]) {
1859		chips.push((base + start, base + end, format!("{icon} #{marker}"), marker));
1860		base += end;
1861	}
1862	let mut spans: SmallVec<Span<'_>, 8> = SmallVec::new();
1863	let mut at = 0;
1864	for (start, end, label, marker) in &chips {
1865		if *start > at {
1866			spans.push(Span::new(&line[at..*start], ink(TEXT)));
1867		}
1868		spans.push(Span::new(label, Style::new().fg(PANEL).bg(attachment_color(*marker)).bold()));
1869		at = *end;
1870	}
1871	if at < line.len() {
1872		spans.push(Span::new(&line[at..], ink(TEXT)));
1873	}
1874	draw_line(frame, x, y, width, &spans);
1875}
1876
1877/// Chrome outside a panel is transparent: no `bg`, so the terminal's own
1878/// background (and any image or blur behind it) shows through. Only the
1879/// panel boxes below opt into a fill.
1880const fn base_style() -> Style {
1881	Style::new().fg(TEXT)
1882}
1883
1884const fn panel_style() -> Style {
1885	Style::new().fg(TEXT).bg(PANEL)
1886}
1887
1888const fn ink(color: Color) -> Style {
1889	Style::new().fg(color)
1890}
1891
1892const fn panel_ink(color: Color) -> Style {
1893	Style::new().fg(color).bg(PANEL)
1894}
examples/chat/welcome.rs (line 278)
257	fn draw_backdrop(&mut self, viewport: Size, clock: Duration, elapsed: f32) {
258		let interval = ambient_interval(clock, BACKDROP_IDLE_INTERVAL);
259		if self
260			.backdrop_at
261			.is_none_or(|rendered_at| clock.saturating_sub(rendered_at) >= interval)
262		{
263			let fade = smooth((elapsed / BACKDROP_FADE).clamp(0.0, 1.0));
264			self
265				.backdrop_frame
266				.fill(Rect::new(0, 0, viewport.width, viewport.height), Style::default());
267			let frame = &mut self.backdrop_frame;
268			let mut buffer = [0_u8; 4];
269			let dim = |color: Color| Color::Rgb(0, 0, 0).lerp(color, fade);
270			self.surface.render(
271				&mut self.backdrop,
272				clock,
273				viewport.width,
274				viewport.height,
275				|x, y, glyph, fg, bg| {
276					let style = Style::new().fg(dim(fg));
277					let style = match bg {
278						Some(bg) => style.bg(dim(bg)),
279						None => style,
280					};
281					frame.put(x, y, glyph.encode_utf8(&mut buffer), style);
282				},
283			);
284			self.backdrop_at = Some(clock);
285		}
286		self.frame.clone_from(&self.backdrop_frame);
287	}
288
289	fn draw_card(&mut self, cols: u16, left: u16, top: u16, elapsed: f32, hover: f32) {
290		let full = cols == CARD_COLS;
291		let logo_left = if full {
292			left + 3
293		} else {
294			left + (cols - LOGO_COLS as u16) / 2
295		};
296		self.logo_origin = (logo_left, top + 2);
297		// Pointer-tracking border glow: the brand gradient sampled by angle
298		// around the card center (the disk's own palette), strongest near
299		// the pointer, scaled by the eased hover amount.
300		let pointer = self.pointer;
301		let center =
302			(f32::from(left) + f32::from(cols) / 2.0, f32::from(top) + f32::from(CARD_ROWS) / 2.0);
303		let edge_at = move |x: u16, y: u16| -> Style {
304			let Some((px, py)) = pointer.filter(|_| hover > 0.02) else {
305				return on_card(CARD_BORDER);
306			};
307			let dx = (f32::from(x) - f32::from(px)) * 0.5;
308			let dy = f32::from(y) - f32::from(py);
309			let glow = hover * (-(dx * dx + dy * dy) / 34.0).exp();
310			if glow < 0.02 {
311				return on_card(CARD_BORDER);
312			}
313			let angle = (f32::from(y) - center.1).atan2((f32::from(x) - center.0) * 0.5);
314			let brand = vec3_color(gradient(angle - elapsed * 0.5));
315			on_card(CARD_BORDER.lerp(brand, glow))
316		};
317		let frame = &mut self.frame;
318		frame.fill(Rect::new(left, top, cols, CARD_ROWS), on_card(TEXT));
319
320		let right = left + cols - 1;
321		let bottom = top + CARD_ROWS - 1;
322		let divider = bottom - 2;
323		let (tl, tr, bl, br, horizontal, vertical) = self.charset.border(Border::Round);
324		let grid = self.charset.grid();
325		let mut glyph = [0_u8; 4];
326		frame.put(left, top, tl.encode_utf8(&mut glyph), edge_at(left, top));
327		frame.put(right, top, tr.encode_utf8(&mut glyph), edge_at(right, top));
328		frame.put(left, divider, grid.middle.0.encode_utf8(&mut glyph), edge_at(left, divider));
329		frame.put(right, divider, grid.middle.2.encode_utf8(&mut glyph), edge_at(right, divider));
330		frame.put(left, bottom, bl.encode_utf8(&mut glyph), edge_at(left, bottom));
331		frame.put(right, bottom, br.encode_utf8(&mut glyph), edge_at(right, bottom));
332		for x in left + 1..right {
333			frame.put(x, top, horizontal.encode_utf8(&mut glyph), edge_at(x, top));
334			frame.put(x, divider, horizontal.encode_utf8(&mut glyph), edge_at(x, divider));
335			frame.put(x, bottom, horizontal.encode_utf8(&mut glyph), edge_at(x, bottom));
336		}
337		for y in top + 1..bottom {
338			if y != divider {
339				frame.put(left, y, vertical.encode_utf8(&mut glyph), edge_at(left, y));
340				frame.put(right, y, vertical.encode_utf8(&mut glyph), edge_at(right, y));
341			}
342		}
343
344		frame.put(left + 2, top, self.title.as_str(), on_card(TEXT_STRONG));
345		if full {
346			frame.put(left + 34, top, " SESSION ORBIT ", on_card(FAINT));
347			draw_dust(frame, left, top, elapsed);
348			draw_sessions(frame, left, top, self.charset);
349			draw_beam(frame, left, top, elapsed);
350		}
351
352		blit_logo(frame, &self.logo, logo_left, top + 2, CARD_BG);
353
354		let footer = divider + 1;
355		frame.fill(Rect::new(left + 1, footer, cols - 2, 1), on_footer(TEXT));
356		if full {
357			frame.put(left + 3, divider, " SHORTCUTS ", on_card(FAINT));
358			let dot = fmts!(" {} ", self.charset.icon(Icon::Enabled));
359			let x = frame.put(left + cols - 21, top, &dot, on_card(GREEN));
360			frame.put(x, top, "rust-analyzer ", on_card(MUTED));
361			draw_full_hints(frame, left, footer);
362		} else {
363			draw_smol_hints(frame, left, cols, footer);
364		}
365	}
366}
367
368impl Default for Welcome {
369	fn default() -> Self {
370		Self::new(Charset::NerdFont)
371	}
372}
373
374fn draw_dust(frame: &mut Frame, left: u16, top: u16, elapsed: f32) {
375	for &(x, y, offset) in &DUST {
376		let pulse = 0.5 + 0.5 * (elapsed * 1.4 + offset).sin();
377		let color = FAINT.lerp(CYAN, pulse * 0.28);
378		frame.put(left + x, top + y, "·", on_card(color));
379	}
380	frame.put(left + 1, top + 7, HORIZON, on_card(FAINT.lerp(INDIGO, 0.16)));
381	frame.put(left + 14, top + 1, "+Z", on_card(FAINT));
382}
383
384fn draw_beam(frame: &mut Frame, left: u16, top: u16, elapsed: f32) {
385	let phase = (elapsed * 9.0) as usize % BEAM.len();
386	for (index, &(x, y, glyph)) in BEAM.iter().enumerate() {
387		let direct = index.abs_diff(phase);
388		let distance = direct.min(BEAM.len() - direct);
389		let color = match distance {
390			0 => TEXT_STRONG,
391			1 => CYAN,
392			_ => FAINT.lerp(INDIGO, 0.34),
393		};
394		frame.put(left + x, top + y, glyph, on_card(color));
395	}
396}
397
398fn draw_sessions(frame: &mut Frame, left: u16, top: u16, charset: Charset) {
399	let (_, _, _, _, _, vertical) = charset.border(Border::Round);
400	let mut glyph = [0_u8; 4];
401	let panel_x = left + 36;
402	frame.put(panel_x, top + 2, "RECENT SESSIONS", on_card(MUTED));
403	frame.put(left + CARD_COLS - 14, top + 2, "4 / LOCAL", on_card(FAINT));
404	for y in top + 4..=top + 10 {
405		frame.put(panel_x, y, vertical.encode_utf8(&mut glyph), on_card(FAINT.lerp(INDIGO, 0.18)));
406	}
407	for (index, (label, age)) in SESSIONS.iter().enumerate() {
408		let y = top + 4 + index as u16 * 2;
409		if index == 0 {
410			frame.fill(Rect::new(panel_x - 2, y, CARD_COLS - 35, 1), on_selected(TEXT));
411			frame.put(panel_x - 2, y, charset.rail(), on_selected(GREEN));
412			frame.put(panel_x, y, charset.radio(true), on_selected(GREEN));
413			frame.put(panel_x + 2, y, age, on_selected(GREEN));
414			frame.put(panel_x + 7, y, label, on_selected(TEXT_STRONG));
415		} else {
416			frame.put(panel_x, y, charset.radio(false), on_card(FAINT));
417			frame.put(panel_x + 2, y, age, on_card(FAINT));
418			frame.put(panel_x + 7, y, label, on_card(MUTED));
419		}
420	}
421}
422
423fn draw_full_hints(frame: &mut Frame, left: u16, y: u16) {
424	frame.put(left + 3, y, "#", on_footer(CYAN));
425	frame.put(left + 5, y, "actions", on_footer(MUTED));
426	frame.put(left + 14, y, "/", on_footer(GREEN));
427	frame.put(left + 16, y, "commands", on_footer(MUTED));
428	frame.put(left + 27, y, "!", on_footer(AMBER));
429	frame.put(left + 29, y, "shell", on_footer(MUTED));
430	frame.put(left + 37, y, "$", on_footer(VIOLET));
431	frame.put(left + 39, y, "python", on_footer(MUTED));
432	frame.put(left + CARD_COLS - 23, y, "↑↓ move", on_footer(FAINT));
433	frame.put(left + CARD_COLS - 13, y, "↵ resume", on_footer(TEXT_STRONG));
434}
435
436fn draw_smol_hints(frame: &mut Frame, left: u16, cols: u16, y: u16) {
437	frame.put(left + 3, y, "#", on_footer(CYAN).bold());
438	frame.put(left + 5, y, "/", on_footer(CYAN).bold());
439	frame.put(left + 7, y, "!", on_footer(AMBER).bold());
440	frame.put(left + 9, y, "$", on_footer(GREEN).bold());
441	frame.put(left + cols - 14, y, "enter", on_footer(FAINT));
442	frame.put(left + cols - 8, y, "resume", on_footer(TEXT_STRONG).bold());
443}
444
445fn blit_logo(frame: &mut Frame, logo: &LogoGrid, left: u16, top: u16, background: Color) {
446	let mut buffer = [0_u8; 4];
447	for (row, cells) in logo.iter().enumerate() {
448		for (column, cell) in cells.iter().enumerate() {
449			let Some((glyph, color)) = cell else { continue };
450			let style = Style::new().fg(*color).bg(background);
451			frame.put(left + column as u16, top + row as u16, glyph.encode_utf8(&mut buffer), style);
452		}
453	}
454}
455
456const fn on_card(fg: Color) -> Style {
457	Style::new().fg(fg).bg(CARD_BG)
458}
459
460const fn on_footer(fg: Color) -> Style {
461	Style::new().fg(fg).bg(FOOTER_BG)
462}
463
464const fn on_selected(fg: Color) -> Style {
465	Style::new().fg(fg).bg(SELECTED_BG)
466}
Source

pub const fn bold(self) -> Self

Enables bold intensity.

Examples found in repository?
examples/chat/welcome.rs (line 437)
436fn draw_smol_hints(frame: &mut Frame, left: u16, cols: u16, y: u16) {
437	frame.put(left + 3, y, "#", on_footer(CYAN).bold());
438	frame.put(left + 5, y, "/", on_footer(CYAN).bold());
439	frame.put(left + 7, y, "!", on_footer(AMBER).bold());
440	frame.put(left + 9, y, "$", on_footer(GREEN).bold());
441	frame.put(left + cols - 14, y, "enter", on_footer(FAINT));
442	frame.put(left + cols - 8, y, "resume", on_footer(TEXT_STRONG).bold());
443}
More examples
Hide additional examples
examples/chat/demo.rs (line 1398)
1389fn draw_command_box(frame: &mut Frame, rect: Rect, charset: Charset) {
1390	draw_box(frame, rect, ink(FAINT), panel_style(), charset);
1391	if rect.width < 4 || rect.height < 4 {
1392		return;
1393	}
1394
1395	let content_x = rect.x + 2;
1396	let content_width = rect.width.saturating_sub(4);
1397	let header = [
1398		Span::new(" PARALLEL TEST RUN ", panel_ink(GREEN).bold()),
1399		Span::new("results append below · live rows in the bottom panel", panel_ink(MUTED)),
1400	];
1401	draw_line(frame, content_x, rect.y + 1, content_width, &header);
1402	let command = [
1403		Span::new("$ ", panel_ink(MUTED)),
1404		Span::new("bun test --parallel=8", panel_ink(CYAN)),
1405		Span::new(" --timeout=30000 --all-workspaces", panel_ink(TEXT)),
1406	];
1407	draw_line(frame, content_x, rect.y + 2, content_width, &command);
1408}
1409
1410/// The live band's shard panel: twelve mutable rows that repaint in place
1411/// every frame and never enter native scrollback.
1412fn draw_live_panel(
1413	frame: &mut Frame,
1414	rows: &mut [LiveRowCache; LIVE_SHARD_ROWS as usize],
1415	label_scratch: &mut StrMut,
1416	rect: Rect,
1417	repaint_chrome: bool,
1418	emitted_shards: u16,
1419	animation_frame: u64,
1420	charset: Charset,
1421) -> bool {
1422	let mut changed = repaint_chrome;
1423	if repaint_chrome {
1424		draw_box(frame, rect, ink(FAINT), panel_style(), charset);
1425	}
1426	if rect.width < 4 || rect.height < 3 {
1427		return changed;
1428	}
1429
1430	if repaint_chrome {
1431		let title = [
1432			Span::new(" LIVE SHARDS ", panel_ink(GREEN).bold()),
1433			Span::new("mutable rows repaint in place ", panel_ink(MUTED)),
1434		];
1435		draw_line(frame, rect.x + 2, rect.y, rect.width.saturating_sub(4), &title);
1436	}
1437	let content_x = rect.x + 2;
1438	let content_width = rect.width.saturating_sub(4);
1439	for row in 0..rect.height.saturating_sub(2) {
1440		let shard = emitted_shards.saturating_add(row).saturating_add(1);
1441		let phase = (u64::from(row) + animation_frame) % 11;
1442		let (prefix_phase, symbol, state, state_style, progress) = match phase {
1443			0 => (
1444				0,
1445				"⠼",
1446				"running",
1447				panel_ink(GREEN).bold(),
1448				(u64::from(row) * 17 + animation_frame * 7) % 100,
1449			),
1450			1..=7 => {
1451				(1, "·", "working", panel_ink(MUTED), (u64::from(row) * 17 + animation_frame * 7) % 100)
1452			},
1453			_ => (2, "·", "queued ", panel_ink(FAINT), 0),
1454		};
1455		let row_y = rect.y + 1 + row;
1456		let right = content_x
1457			.saturating_add(content_width)
1458			.min(frame.size().width);
1459		let cache = &mut rows[usize::from(row)];
1460		let prefix_changed = repaint_chrome
1461			|| !cache.prefix_valid
1462			|| cache.prefix_shard != shard
1463			|| cache.prefix_phase != prefix_phase;
1464		changed |= prefix_changed;
1465		let label_x = if prefix_changed {
1466			let prefix = fmts!(" {symbol} shard {shard:03} ");
1467			let prefix_width = prefix.len().saturating_sub(symbol.len()).saturating_add(1);
1468			let next_x = content_x
1469				.saturating_add(u16::try_from(prefix_width).unwrap_or(u16::MAX))
1470				.saturating_add(u16::try_from(state.len()).unwrap_or(u16::MAX))
1471				.saturating_add(2)
1472				.min(right);
1473			if !repaint_chrome && cache.label_x != next_x {
1474				clear_cached_label(frame, cache, row_y, right);
1475			}
1476			let next_x = draw_line(frame, content_x, row_y, content_width, &[
1477				Span::new(prefix.as_str(), state_style),
1478				Span::new(state, state_style),
1479				Span::new("  ", panel_ink(FAINT)),
1480			]);
1481			cache.prefix_shard = shard;
1482			cache.prefix_phase = prefix_phase;
1483			cache.prefix_valid = true;
1484			next_x
1485		} else {
1486			cache.label_x
1487		};
1488		let moved = cache.label_x != label_x;
1489		let label_changed = repaint_chrome
1490			|| moved
1491			|| !cache.label_valid
1492			|| cache.label_shard != shard
1493			|| cache.label_progress != progress;
1494		changed |= label_changed;
1495		if label_changed {
1496			label_scratch.truncate(0);
1497			write!(label_scratch, "workspace-{shard:03}.test.ts  [{progress:>3}%]")
1498				.expect("shard label formatting is infallible");
1499			let resized = cache.label.len() != label_scratch.len();
1500			if !repaint_chrome && resized && !moved {
1501				clear_cached_label(frame, cache, row_y, right);
1502			}
1503			let width = right.saturating_sub(label_x);
1504			if repaint_chrome || moved || resized {
1505				frame.put_clipped(label_x, row_y, width, label_scratch.as_str(), panel_ink(MUTED));
1506			} else {
1507				draw_ascii_changes(
1508					frame,
1509					label_x,
1510					row_y,
1511					width,
1512					cache.label.as_str(),
1513					label_scratch.as_str(),
1514					panel_ink(MUTED),
1515				);
1516			}
1517			std::mem::swap(&mut cache.label, label_scratch);
1518			cache.label_shard = shard;
1519			cache.label_progress = progress;
1520			cache.label_valid = true;
1521		}
1522		cache.label_x = label_x;
1523	}
1524	changed
1525}
1526
1527fn clear_cached_label(frame: &mut Frame, cache: &LiveRowCache, y: u16, right: u16) {
1528	if cache.label.is_empty() {
1529		return;
1530	}
1531	let width = u16::try_from(cache.label.len())
1532		.unwrap_or(u16::MAX)
1533		.min(right.saturating_sub(cache.label_x));
1534	frame.fill(Rect::new(cache.label_x, y, width, 1), panel_style());
1535}
1536
1537/// Repaints only changed byte runs within an equal-length ASCII label.
1538fn draw_ascii_changes(
1539	frame: &mut Frame,
1540	x: u16,
1541	y: u16,
1542	width: u16,
1543	previous: &str,
1544	next: &str,
1545	style: Style,
1546) {
1547	if width == 0 || previous == next {
1548		return;
1549	}
1550	if previous.len() != next.len() || !previous.is_ascii() || !next.is_ascii() {
1551		frame.put_clipped(x, y, width, next, style);
1552		return;
1553	}
1554	let previous = previous.as_bytes();
1555	let next_bytes = next.as_bytes();
1556	let limit = previous.len().min(usize::from(width));
1557	let mut index = 0;
1558	while index < limit {
1559		while index < limit && previous[index] == next_bytes[index] {
1560			index += 1;
1561		}
1562		let start = index;
1563		while index < limit && previous[index] != next_bytes[index] {
1564			index += 1;
1565		}
1566		if start < index {
1567			let offset = u16::try_from(start).unwrap_or(u16::MAX);
1568			frame.put_clipped(
1569				x.saturating_add(offset),
1570				y,
1571				u16::try_from(index - start).unwrap_or(u16::MAX),
1572				&next[start..index],
1573				style,
1574			);
1575		}
1576	}
1577}
1578
1579fn draw_edit_box(frame: &mut Frame, rect: Rect, charset: Charset) {
1580	draw_box(frame, rect, ink(FAINT), panel_style(), charset);
1581	if rect.width < 8 || rect.height < EDIT_BOX_HEIGHT {
1582		return;
1583	}
1584
1585	let title = [
1586		Span::new(" Live ", panel_ink(GREEN).bold()),
1587		Span::new("band · selection semantics ", panel_ink(CYAN)),
1588	];
1589	draw_line(frame, rect.x + 2, rect.y, rect.width.saturating_sub(4), &title);
1590	draw_line(frame, rect.x + 2, rect.y + 1, rect.width.saturating_sub(4), &[
1591		Span::new(charset.check(), panel_ink(GREEN).bold()),
1592		Span::new(" ", panel_ink(GREEN)),
1593		Span::new("Transcript selections ride with the text", panel_ink(TEXT)),
1594	]);
1595	draw_line(frame, rect.x + 2, rect.y + 2, rect.width.saturating_sub(4), &[Span::new(
1596		"  margin commits pin the band on kitty-class terminals",
1597		panel_ink(MUTED),
1598	)]);
1599}
1600
1601/// Paints a submitted message: the prompt gutter, then the rendered
1602/// Markdown document blitted beside it (or the raw lines when the text
1603/// isn't renderable as Markdown).
1604fn draw_submission(frame: &mut Frame, y: u16, submission: &Submission, charset: Charset) {
1605	if frame.size().width < 4 {
1606		return;
1607	}
1608	let Some(view) = &submission.view else {
1609		for (offset, line) in submission.text.split('\n').enumerate() {
1610			let Ok(offset) = u16::try_from(offset) else {
1611				break;
1612			};
1613			let row = y.saturating_add(offset);
1614			if row >= frame.size().height {
1615				break;
1616			}
1617			let prompt = if offset == 0 { charset.cursor() } else { "  " };
1618			let text_x = frame.put(1, row, prompt, ink(GREEN).bold());
1619			let width = frame
1620				.size()
1621				.width
1622				.saturating_sub(1)
1623				.saturating_sub(text_x.saturating_sub(1));
1624			draw_submission_text(frame, text_x, row, width, line, charset);
1625		}
1626		return;
1627	};
1628	frame.put(1, y, charset.cursor(), ink(GREEN).bold());
1629	frame.blit(view.frame(), 0, view.height(), 3, y);
1630}
1631fn explicit_line_count(text: &str) -> u16 {
1632	u16::try_from(
1633		text
1634			.bytes()
1635			.filter(|byte| *byte == b'\n')
1636			.count()
1637			.saturating_add(1),
1638	)
1639	.unwrap_or(u16::MAX)
1640}
1641
1642/// Paints a rounded panel box through the tier's border glyphs.
1643fn draw_box(frame: &mut Frame, rect: Rect, border: Style, fill: Style, charset: Charset) {
1644	if rect.width == 0 || rect.height == 0 {
1645		return;
1646	}
1647	let (tl, tr, _, _, horizontal, vertical) = charset.border(Border::Round);
1648	frame.fill(rect, fill);
1649	let mut glyph = [0_u8; 4];
1650	if rect.width == 1 {
1651		frame.put(rect.x, rect.y, vertical.encode_utf8(&mut glyph), border);
1652		return;
1653	}
1654
1655	let right = rect.x + rect.width - 1;
1656	let bottom = rect.y + rect.height - 1;
1657	frame.put(rect.x, rect.y, tl.encode_utf8(&mut glyph), border);
1658	frame.put(right, rect.y, tr.encode_utf8(&mut glyph), border);
1659	for x in rect.x + 1..right {
1660		frame.put(x, rect.y, horizontal.encode_utf8(&mut glyph), border);
1661	}
1662
1663	if rect.height > 1 {
1664		draw_box_bottom(frame, rect, border, charset);
1665	}
1666	for row in rect.y + 1..bottom {
1667		frame.put(rect.x, row, vertical.encode_utf8(&mut glyph), border);
1668		frame.put(right, row, vertical.encode_utf8(&mut glyph), border);
1669	}
1670}
1671
1672fn draw_box_bottom(frame: &mut Frame, rect: Rect, border: Style, charset: Charset) {
1673	if rect.width < 2 || rect.height < 2 {
1674		return;
1675	}
1676	let (_, _, bl, br, horizontal, _) = charset.border(Border::Round);
1677	let mut glyph = [0_u8; 4];
1678	let right = rect.x + rect.width - 1;
1679	let bottom = rect.y + rect.height - 1;
1680	frame.put(rect.x, bottom, bl.encode_utf8(&mut glyph), border);
1681	frame.put(right, bottom, br.encode_utf8(&mut glyph), border);
1682	for x in rect.x + 1..right {
1683		frame.put(x, bottom, horizontal.encode_utf8(&mut glyph), border);
1684	}
1685}
1686
1687fn draw_line(frame: &mut Frame, x: u16, y: u16, width: u16, spans: &[Span<'_>]) -> u16 {
1688	let right = x.saturating_add(width).min(frame.size().width);
1689	let mut column = x;
1690	for span in spans {
1691		column = frame.put_clipped(column, y, right.saturating_sub(column), span.text, span.style);
1692		if column >= right {
1693			break;
1694		}
1695	}
1696	column
1697}
1698
1699/// Flows `spans` grapheme-exact across the rect like a bare terminal,
1700/// preserving all whitespace and flagging each exactly-filled row boundary
1701/// soft so native selection copies the paragraph as one unbroken line.
1702/// Returns the rows used.
1703fn draw_flowed(frame: &mut Frame, rect: Rect, spans: &[Span<'_>]) -> u16 {
1704	if rect.width == 0 || rect.height == 0 {
1705		return 0;
1706	}
1707	let full_row = rect.x == 0 && rect.width == frame.size().width;
1708	let mut row = 0_u16;
1709	let mut column = 0_u16;
1710	let mut drew_anything = false;
1711
1712	for span in spans {
1713		for grapheme in xutf::graphemes_str(span.text) {
1714			let grapheme_width = visible_width(grapheme);
1715			if grapheme_width == 0 || grapheme_width > rect.width {
1716				continue;
1717			}
1718			if column.saturating_add(grapheme_width) > rect.width {
1719				// Only an exactly-filled row is byte-joinable by autowrap.
1720				if full_row && column == rect.width {
1721					frame.set_soft_wrap(rect.y.saturating_add(row));
1722				}
1723				row += 1;
1724				column = 0;
1725			}
1726			if row >= rect.height {
1727				return rect.height;
1728			}
1729			frame.put(rect.x + column, rect.y + row, grapheme, span.style);
1730			column += grapheme_width;
1731			drew_anything = true;
1732		}
1733	}
1734
1735	if drew_anything { row + 1 } else { 0 }
1736}
1737
1738fn visible_width(text: &str) -> u16 {
1739	u16::try_from(xutf::width_str(text)).unwrap_or(u16::MAX)
1740}
1741
1742/// Finds the first `<ref image=N/>` tag in submitted text: its byte range
1743/// plus the marker number `N`.
1744fn next_ref_tag(text: &str) -> Option<(usize, usize, usize)> {
1745	const HEAD: &str = "<ref image=";
1746	let mut from = 0;
1747	while let Some(at) = text[from..].find(HEAD) {
1748		let start = from + at;
1749		let body = &text[start + HEAD.len()..];
1750		let digits = body.bytes().take_while(u8::is_ascii_digit).count();
1751		if digits > 0 && body[digits..].starts_with("/>") {
1752			let marker = body[..digits].parse().unwrap_or(usize::MAX);
1753			return Some((start, start + HEAD.len() + digits + 2, marker));
1754		}
1755		from = start + HEAD.len();
1756	}
1757	None
1758}
1759
1760/// Splits one composer input row into base-styled text and attachment
1761/// chips, chip styling winning over any overlapping XML run.
1762///
1763/// Chips are located through the buffer's atomic ranges — like the XML
1764/// pass, styling happens at paint time, and typed lookalike text is never
1765/// recolored.
1766fn push_row_spans<'a>(
1767	editor: &Editor,
1768	row: &'a str,
1769	runs: &[SyntaxRun],
1770	spans: &mut SmallVec<Span<'a>, 16>,
1771) {
1772	let text = editor.text();
1773	let buffer_start = text.as_ptr() as usize;
1774	let row_start = (row.as_ptr() as usize).saturating_sub(buffer_start);
1775	let row_end = row_start + row.len();
1776	// Chip segments clipped to this row; style derives from the FULL atom
1777	// text, so a chip wrapped across rows keeps its color on every row.
1778	let mut chips: SmallVec<(usize, usize, Style), 4> = SmallVec::new();
1779	for (start, end) in editor.atom_ranges() {
1780		let from = start.max(row_start);
1781		let to = end.min(row_end);
1782		if from < to {
1783			chips.push((from - row_start, to - row_start, chip_style(&text[start..end])));
1784		}
1785	}
1786	// The style and extent of the base segment covering `at`: its XML run,
1787	// or plain text up to the next run.
1788	let base = |at: usize| {
1789		runs
1790			.iter()
1791			.find(|run| run.start <= at && at < run.end)
1792			.map_or_else(
1793				|| {
1794					let next = runs
1795						.iter()
1796						.map(|run| run.start)
1797						.filter(|start| *start > at)
1798						.min()
1799						.unwrap_or(row.len());
1800					(next, base_style())
1801				},
1802				|run| (run.end, run.style),
1803			)
1804	};
1805	fn emit<'a>(
1806		row: &'a str,
1807		base: &impl Fn(usize) -> (usize, Style),
1808		from: usize,
1809		to: usize,
1810		spans: &mut SmallVec<Span<'a>, 16>,
1811	) {
1812		let mut at = from;
1813		while at < to {
1814			let (run_end, style) = base(at);
1815			let end = run_end.min(to);
1816			spans.push(Span::new(&row[at..end], style));
1817			at = end;
1818		}
1819	}
1820	let mut at = 0;
1821	for (start, end, style) in chips {
1822		emit(row, &base, at, start, spans);
1823		spans.push(Span::new(&row[start..end], style));
1824		at = end;
1825	}
1826	emit(row, &base, at, row.len(), spans);
1827}
1828
1829/// Style for one atomic chip: a trailing `#N` selects the marker's
1830/// identity color; other atoms stay plain.
1831fn chip_style(chip: &str) -> Style {
1832	let Some(hash) = chip.rfind('#') else {
1833		return base_style();
1834	};
1835	let digits = &chip[hash + 1..];
1836	if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
1837		return base_style();
1838	}
1839	match digits.parse::<usize>() {
1840		Ok(marker) if marker > 0 => ink(attachment_color(marker)).bold(),
1841		_ => base_style(),
1842	}
1843}
1844
1845/// Paints one transcript line, rendering each `<ref image=N/>` tag as a
1846/// compact `<icon> #N` pill filled with the attachment's identity color.
1847fn draw_submission_text(
1848	frame: &mut Frame,
1849	x: u16,
1850	y: u16,
1851	width: u16,
1852	line: &str,
1853	charset: Charset,
1854) {
1855	let icon = charset.icon(Icon::Image);
1856	let mut chips: SmallVec<(usize, usize, String, usize), 4> = SmallVec::new();
1857	let mut base = 0;
1858	while let Some((start, end, marker)) = next_ref_tag(&line[base..]) {
1859		chips.push((base + start, base + end, format!("{icon} #{marker}"), marker));
1860		base += end;
1861	}
1862	let mut spans: SmallVec<Span<'_>, 8> = SmallVec::new();
1863	let mut at = 0;
1864	for (start, end, label, marker) in &chips {
1865		if *start > at {
1866			spans.push(Span::new(&line[at..*start], ink(TEXT)));
1867		}
1868		spans.push(Span::new(label, Style::new().fg(PANEL).bg(attachment_color(*marker)).bold()));
1869		at = *end;
1870	}
1871	if at < line.len() {
1872		spans.push(Span::new(&line[at..], ink(TEXT)));
1873	}
1874	draw_line(frame, x, y, width, &spans);
1875}
examples/footers.rs (line 207)
199fn compose(scene: &Scene) -> Frame {
200	let height = STUDIES
201		.iter()
202		.map(|study| study.rows + 3)
203		.fold(3_u16, u16::saturating_add);
204	let mut frame = Frame::new(Size::new(scene.width, height));
205	frame.fill(Rect::new(0, 0, scene.width, height), ink(TEXT));
206
207	let column = frame.put(1, 0, "composer footer studies", ink(TEXT).bold());
208	frame.put(
209		column.saturating_add(2),
210		0,
211		"split + air gap, six session-title placements",
212		ink(MUTED),
213	);
214	frame.put(1, 1, "↑/↓ scroll · PgUp/PgDn page · Home/End jump · q quits", ink(FAINT));
215
216	let mut y = 3_u16;
217	for (index, study) in STUDIES.iter().enumerate() {
218		let number = fmts!("{:>2} ", index + 1);
219		let mut column = frame.put(1, y, &number, ink(GOLD).bold());
220		column = frame.put(column, y, study.title, ink(TEXT).bold());
221		column = frame.put(column, y, "  ", ink(FAINT));
222		frame.put(column, y, study.note, ink(MUTED));
223		(study.draw)(&mut frame, y + 1, scene);
224		y = y.saturating_add(study.rows + 3);
225	}
226	frame
227}
228
229// ── studies ─────────────────────────────────────────────────────────────────
230
231/// 1: the pi layout verbatim — a bordered composer whose top border
232/// carries the session band on the left and the title on the right, with
233/// the spinner narrating intent on its own row above the box.
234fn study_pi_parity(frame: &mut Frame, y: u16, scene: &Scene) {
235	draw_working_spin(frame, 1, y, scene);
236	let (tl, tr, bl, br, horizontal, vertical) = border_glyphs(scene.charset);
237	let right = scene.right_edge();
238	draw_border_row(frame, y + 1, scene, tl, tr, horizontal);
239	let segments = [model(scene), omp_brand(scene), git(scene), context(scene), cost()];
240	draw_band(frame, 2, y + 1, scene, &segments);
241	let band_end = 2_u16.saturating_add(band_width(scene, &segments));
242	draw_border_title(frame, y + 1, scene, band_end.saturating_add(2));
243	frame.put(0, y + 2, vertical, ink(FAINT));
244	frame.put(2, y + 2, beam(scene.charset), ink(TEXT));
245	frame.put(right, y + 2, vertical, ink(FAINT));
246	draw_border_row(frame, y + 3, scene, bl, br, horizontal);
247}
248
249/// 2: split + air gap as picked, with the title moving into the air row
250/// so the breathing space doubles as identity.
251fn study_gap_title(frame: &mut Frame, y: u16, scene: &Scene) {
252	draw_working(frame, 1, y, scene);
253	let title = fit_title(scene, scene.width.saturating_sub(2));
254	let x = scene
255		.width
256		.saturating_sub(width_of(&title).saturating_add(1));
257	frame.put(x, y + 1, &title, ink(FAINT).italic());
258	draw_split_bands(frame, y + 2, scene);
259	draw_input(frame, 0, y + 3, scene);
260}
261
262/// 3: the title joins the left band beside the brand segment, so the
263/// band row itself answers "what session is this"; session facts keep
264/// the right cap.
265fn study_band_title(frame: &mut Frame, y: u16, scene: &Scene) {
266	draw_working(frame, 1, y, scene);
267	let right = [model(scene), git(scene), context(scene), Seg::new(COST_SHORT, PURPLE)];
268	let right_width = band_width(scene, &right);
269	let brand_seg = brand(scene);
270	let (_, separator, _) = band_chrome(scene.charset);
271	let fixed = band_width(scene, std::slice::from_ref(&brand_seg))
272		.saturating_add(width_of(separator).saturating_add(2));
273	let budget = scene
274		.width
275		.saturating_sub(right_width.saturating_add(2))
276		.saturating_sub(fixed);
277	let left = [brand_seg, Seg::new(fit_title(scene, budget), TEXT)];
278	draw_band(frame, 0, y + 2, scene, &left);
279	draw_band(frame, scene.width.saturating_sub(right_width), y + 2, scene, &right);
280	draw_input(frame, 0, y + 3, scene);
281}
282
283/// 4: split + air untouched; the title borrows the prompt row's right
284/// edge and would yield to long input lines.
285fn study_prompt_title(frame: &mut Frame, y: u16, scene: &Scene) {
286	draw_working(frame, 1, y, scene);
287	draw_split_bands(frame, y + 2, scene);
288	draw_input(frame, 0, y + 3, scene);
289	let title = fit_title(scene, scene.width.saturating_sub(8));
290	let x = scene
291		.width
292		.saturating_sub(width_of(&title).saturating_add(1));
293	frame.put(x, y + 3, &title, ink(FAINT).italic());
294}
295
296/// 5: the title gets its own dim row above the narration, heading the
297/// whole live block like a section title.
298fn study_crown(frame: &mut Frame, y: u16, scene: &Scene) {
299	let title = fit_title(scene, scene.width.saturating_sub(2));
300	frame.put(1, y, &title, ink(MUTED).bold());
301	draw_working(frame, 1, y + 1, scene);
302	draw_split_bands(frame, y + 3, scene);
303	draw_input(frame, 0, y + 4, scene);
304}
Source

pub const fn dim(self) -> Self

Enables faint intensity.

Examples found in repository?
examples/footers.rs (line 460)
452fn draw_band(frame: &mut Frame, x: u16, y: u16, scene: &Scene, segments: &[Seg]) {
453	let (left_cap, separator, right_cap) = band_chrome(scene.charset);
454	let base = Style::new().fg(TEXT).bg(BAND_BG);
455	let edge = ink(BAND_BG);
456	let mut column = frame.put(x, y, left_cap, edge);
457	column = frame.put(column, y, " ", base);
458	for (index, segment) in segments.iter().enumerate() {
459		if index > 0 {
460			column = frame.put(column, y, " ", base.dim());
461			column = frame.put(column, y, separator, base.dim());
462			column = frame.put(column, y, " ", base.dim());
463		}
464		column = frame.put(column, y, &segment.label, base.fg(segment.color));
465	}
466	column = frame.put(column, y, " ", base);
467	frame.put(column, y, right_cap, edge);
468}
More examples
Hide additional examples
examples/chat/demo.rs (line 465)
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	}
Source

pub const fn italic(self) -> Self

Enables italics.

Examples found in repository?
examples/footers.rs (line 257)
251fn study_gap_title(frame: &mut Frame, y: u16, scene: &Scene) {
252	draw_working(frame, 1, y, scene);
253	let title = fit_title(scene, scene.width.saturating_sub(2));
254	let x = scene
255		.width
256		.saturating_sub(width_of(&title).saturating_add(1));
257	frame.put(x, y + 1, &title, ink(FAINT).italic());
258	draw_split_bands(frame, y + 2, scene);
259	draw_input(frame, 0, y + 3, scene);
260}
261
262/// 3: the title joins the left band beside the brand segment, so the
263/// band row itself answers "what session is this"; session facts keep
264/// the right cap.
265fn study_band_title(frame: &mut Frame, y: u16, scene: &Scene) {
266	draw_working(frame, 1, y, scene);
267	let right = [model(scene), git(scene), context(scene), Seg::new(COST_SHORT, PURPLE)];
268	let right_width = band_width(scene, &right);
269	let brand_seg = brand(scene);
270	let (_, separator, _) = band_chrome(scene.charset);
271	let fixed = band_width(scene, std::slice::from_ref(&brand_seg))
272		.saturating_add(width_of(separator).saturating_add(2));
273	let budget = scene
274		.width
275		.saturating_sub(right_width.saturating_add(2))
276		.saturating_sub(fixed);
277	let left = [brand_seg, Seg::new(fit_title(scene, budget), TEXT)];
278	draw_band(frame, 0, y + 2, scene, &left);
279	draw_band(frame, scene.width.saturating_sub(right_width), y + 2, scene, &right);
280	draw_input(frame, 0, y + 3, scene);
281}
282
283/// 4: split + air untouched; the title borrows the prompt row's right
284/// edge and would yield to long input lines.
285fn study_prompt_title(frame: &mut Frame, y: u16, scene: &Scene) {
286	draw_working(frame, 1, y, scene);
287	draw_split_bands(frame, y + 2, scene);
288	draw_input(frame, 0, y + 3, scene);
289	let title = fit_title(scene, scene.width.saturating_sub(8));
290	let x = scene
291		.width
292		.saturating_sub(width_of(&title).saturating_add(1));
293	frame.put(x, y + 3, &title, ink(FAINT).italic());
294}
More examples
Hide additional examples
examples/chat/demo.rs (line 1384)
1377	fn draw_session_title(frame: &mut Frame, y: u16, right_inset: u16) {
1378		let width = frame.size().width.saturating_sub(right_inset);
1379		let title_width = visible_width(SESSION_TITLE);
1380		if y >= frame.size().height || width < title_width.saturating_add(2) {
1381			return;
1382		}
1383		let x = width.saturating_sub(title_width.saturating_add(1));
1384		draw_line(frame, x, y, title_width, &[Span::new(SESSION_TITLE, ink(FAINT).italic())]);
1385	}
1386}
1387
1388/// The closed four-row command box that opens the transcript.
1389fn draw_command_box(frame: &mut Frame, rect: Rect, charset: Charset) {
1390	draw_box(frame, rect, ink(FAINT), panel_style(), charset);
1391	if rect.width < 4 || rect.height < 4 {
1392		return;
1393	}
1394
1395	let content_x = rect.x + 2;
1396	let content_width = rect.width.saturating_sub(4);
1397	let header = [
1398		Span::new(" PARALLEL TEST RUN ", panel_ink(GREEN).bold()),
1399		Span::new("results append below · live rows in the bottom panel", panel_ink(MUTED)),
1400	];
1401	draw_line(frame, content_x, rect.y + 1, content_width, &header);
1402	let command = [
1403		Span::new("$ ", panel_ink(MUTED)),
1404		Span::new("bun test --parallel=8", panel_ink(CYAN)),
1405		Span::new(" --timeout=30000 --all-workspaces", panel_ink(TEXT)),
1406	];
1407	draw_line(frame, content_x, rect.y + 2, content_width, &command);
1408}
1409
1410/// The live band's shard panel: twelve mutable rows that repaint in place
1411/// every frame and never enter native scrollback.
1412fn draw_live_panel(
1413	frame: &mut Frame,
1414	rows: &mut [LiveRowCache; LIVE_SHARD_ROWS as usize],
1415	label_scratch: &mut StrMut,
1416	rect: Rect,
1417	repaint_chrome: bool,
1418	emitted_shards: u16,
1419	animation_frame: u64,
1420	charset: Charset,
1421) -> bool {
1422	let mut changed = repaint_chrome;
1423	if repaint_chrome {
1424		draw_box(frame, rect, ink(FAINT), panel_style(), charset);
1425	}
1426	if rect.width < 4 || rect.height < 3 {
1427		return changed;
1428	}
1429
1430	if repaint_chrome {
1431		let title = [
1432			Span::new(" LIVE SHARDS ", panel_ink(GREEN).bold()),
1433			Span::new("mutable rows repaint in place ", panel_ink(MUTED)),
1434		];
1435		draw_line(frame, rect.x + 2, rect.y, rect.width.saturating_sub(4), &title);
1436	}
1437	let content_x = rect.x + 2;
1438	let content_width = rect.width.saturating_sub(4);
1439	for row in 0..rect.height.saturating_sub(2) {
1440		let shard = emitted_shards.saturating_add(row).saturating_add(1);
1441		let phase = (u64::from(row) + animation_frame) % 11;
1442		let (prefix_phase, symbol, state, state_style, progress) = match phase {
1443			0 => (
1444				0,
1445				"⠼",
1446				"running",
1447				panel_ink(GREEN).bold(),
1448				(u64::from(row) * 17 + animation_frame * 7) % 100,
1449			),
1450			1..=7 => {
1451				(1, "·", "working", panel_ink(MUTED), (u64::from(row) * 17 + animation_frame * 7) % 100)
1452			},
1453			_ => (2, "·", "queued ", panel_ink(FAINT), 0),
1454		};
1455		let row_y = rect.y + 1 + row;
1456		let right = content_x
1457			.saturating_add(content_width)
1458			.min(frame.size().width);
1459		let cache = &mut rows[usize::from(row)];
1460		let prefix_changed = repaint_chrome
1461			|| !cache.prefix_valid
1462			|| cache.prefix_shard != shard
1463			|| cache.prefix_phase != prefix_phase;
1464		changed |= prefix_changed;
1465		let label_x = if prefix_changed {
1466			let prefix = fmts!(" {symbol} shard {shard:03} ");
1467			let prefix_width = prefix.len().saturating_sub(symbol.len()).saturating_add(1);
1468			let next_x = content_x
1469				.saturating_add(u16::try_from(prefix_width).unwrap_or(u16::MAX))
1470				.saturating_add(u16::try_from(state.len()).unwrap_or(u16::MAX))
1471				.saturating_add(2)
1472				.min(right);
1473			if !repaint_chrome && cache.label_x != next_x {
1474				clear_cached_label(frame, cache, row_y, right);
1475			}
1476			let next_x = draw_line(frame, content_x, row_y, content_width, &[
1477				Span::new(prefix.as_str(), state_style),
1478				Span::new(state, state_style),
1479				Span::new("  ", panel_ink(FAINT)),
1480			]);
1481			cache.prefix_shard = shard;
1482			cache.prefix_phase = prefix_phase;
1483			cache.prefix_valid = true;
1484			next_x
1485		} else {
1486			cache.label_x
1487		};
1488		let moved = cache.label_x != label_x;
1489		let label_changed = repaint_chrome
1490			|| moved
1491			|| !cache.label_valid
1492			|| cache.label_shard != shard
1493			|| cache.label_progress != progress;
1494		changed |= label_changed;
1495		if label_changed {
1496			label_scratch.truncate(0);
1497			write!(label_scratch, "workspace-{shard:03}.test.ts  [{progress:>3}%]")
1498				.expect("shard label formatting is infallible");
1499			let resized = cache.label.len() != label_scratch.len();
1500			if !repaint_chrome && resized && !moved {
1501				clear_cached_label(frame, cache, row_y, right);
1502			}
1503			let width = right.saturating_sub(label_x);
1504			if repaint_chrome || moved || resized {
1505				frame.put_clipped(label_x, row_y, width, label_scratch.as_str(), panel_ink(MUTED));
1506			} else {
1507				draw_ascii_changes(
1508					frame,
1509					label_x,
1510					row_y,
1511					width,
1512					cache.label.as_str(),
1513					label_scratch.as_str(),
1514					panel_ink(MUTED),
1515				);
1516			}
1517			std::mem::swap(&mut cache.label, label_scratch);
1518			cache.label_shard = shard;
1519			cache.label_progress = progress;
1520			cache.label_valid = true;
1521		}
1522		cache.label_x = label_x;
1523	}
1524	changed
1525}
1526
1527fn clear_cached_label(frame: &mut Frame, cache: &LiveRowCache, y: u16, right: u16) {
1528	if cache.label.is_empty() {
1529		return;
1530	}
1531	let width = u16::try_from(cache.label.len())
1532		.unwrap_or(u16::MAX)
1533		.min(right.saturating_sub(cache.label_x));
1534	frame.fill(Rect::new(cache.label_x, y, width, 1), panel_style());
1535}
1536
1537/// Repaints only changed byte runs within an equal-length ASCII label.
1538fn draw_ascii_changes(
1539	frame: &mut Frame,
1540	x: u16,
1541	y: u16,
1542	width: u16,
1543	previous: &str,
1544	next: &str,
1545	style: Style,
1546) {
1547	if width == 0 || previous == next {
1548		return;
1549	}
1550	if previous.len() != next.len() || !previous.is_ascii() || !next.is_ascii() {
1551		frame.put_clipped(x, y, width, next, style);
1552		return;
1553	}
1554	let previous = previous.as_bytes();
1555	let next_bytes = next.as_bytes();
1556	let limit = previous.len().min(usize::from(width));
1557	let mut index = 0;
1558	while index < limit {
1559		while index < limit && previous[index] == next_bytes[index] {
1560			index += 1;
1561		}
1562		let start = index;
1563		while index < limit && previous[index] != next_bytes[index] {
1564			index += 1;
1565		}
1566		if start < index {
1567			let offset = u16::try_from(start).unwrap_or(u16::MAX);
1568			frame.put_clipped(
1569				x.saturating_add(offset),
1570				y,
1571				u16::try_from(index - start).unwrap_or(u16::MAX),
1572				&next[start..index],
1573				style,
1574			);
1575		}
1576	}
1577}
1578
1579fn draw_edit_box(frame: &mut Frame, rect: Rect, charset: Charset) {
1580	draw_box(frame, rect, ink(FAINT), panel_style(), charset);
1581	if rect.width < 8 || rect.height < EDIT_BOX_HEIGHT {
1582		return;
1583	}
1584
1585	let title = [
1586		Span::new(" Live ", panel_ink(GREEN).bold()),
1587		Span::new("band · selection semantics ", panel_ink(CYAN)),
1588	];
1589	draw_line(frame, rect.x + 2, rect.y, rect.width.saturating_sub(4), &title);
1590	draw_line(frame, rect.x + 2, rect.y + 1, rect.width.saturating_sub(4), &[
1591		Span::new(charset.check(), panel_ink(GREEN).bold()),
1592		Span::new(" ", panel_ink(GREEN)),
1593		Span::new("Transcript selections ride with the text", panel_ink(TEXT)),
1594	]);
1595	draw_line(frame, rect.x + 2, rect.y + 2, rect.width.saturating_sub(4), &[Span::new(
1596		"  margin commits pin the band on kitty-class terminals",
1597		panel_ink(MUTED),
1598	)]);
1599}
1600
1601/// Paints a submitted message: the prompt gutter, then the rendered
1602/// Markdown document blitted beside it (or the raw lines when the text
1603/// isn't renderable as Markdown).
1604fn draw_submission(frame: &mut Frame, y: u16, submission: &Submission, charset: Charset) {
1605	if frame.size().width < 4 {
1606		return;
1607	}
1608	let Some(view) = &submission.view else {
1609		for (offset, line) in submission.text.split('\n').enumerate() {
1610			let Ok(offset) = u16::try_from(offset) else {
1611				break;
1612			};
1613			let row = y.saturating_add(offset);
1614			if row >= frame.size().height {
1615				break;
1616			}
1617			let prompt = if offset == 0 { charset.cursor() } else { "  " };
1618			let text_x = frame.put(1, row, prompt, ink(GREEN).bold());
1619			let width = frame
1620				.size()
1621				.width
1622				.saturating_sub(1)
1623				.saturating_sub(text_x.saturating_sub(1));
1624			draw_submission_text(frame, text_x, row, width, line, charset);
1625		}
1626		return;
1627	};
1628	frame.put(1, y, charset.cursor(), ink(GREEN).bold());
1629	frame.blit(view.frame(), 0, view.height(), 3, y);
1630}
1631fn explicit_line_count(text: &str) -> u16 {
1632	u16::try_from(
1633		text
1634			.bytes()
1635			.filter(|byte| *byte == b'\n')
1636			.count()
1637			.saturating_add(1),
1638	)
1639	.unwrap_or(u16::MAX)
1640}
1641
1642/// Paints a rounded panel box through the tier's border glyphs.
1643fn draw_box(frame: &mut Frame, rect: Rect, border: Style, fill: Style, charset: Charset) {
1644	if rect.width == 0 || rect.height == 0 {
1645		return;
1646	}
1647	let (tl, tr, _, _, horizontal, vertical) = charset.border(Border::Round);
1648	frame.fill(rect, fill);
1649	let mut glyph = [0_u8; 4];
1650	if rect.width == 1 {
1651		frame.put(rect.x, rect.y, vertical.encode_utf8(&mut glyph), border);
1652		return;
1653	}
1654
1655	let right = rect.x + rect.width - 1;
1656	let bottom = rect.y + rect.height - 1;
1657	frame.put(rect.x, rect.y, tl.encode_utf8(&mut glyph), border);
1658	frame.put(right, rect.y, tr.encode_utf8(&mut glyph), border);
1659	for x in rect.x + 1..right {
1660		frame.put(x, rect.y, horizontal.encode_utf8(&mut glyph), border);
1661	}
1662
1663	if rect.height > 1 {
1664		draw_box_bottom(frame, rect, border, charset);
1665	}
1666	for row in rect.y + 1..bottom {
1667		frame.put(rect.x, row, vertical.encode_utf8(&mut glyph), border);
1668		frame.put(right, row, vertical.encode_utf8(&mut glyph), border);
1669	}
1670}
1671
1672fn draw_box_bottom(frame: &mut Frame, rect: Rect, border: Style, charset: Charset) {
1673	if rect.width < 2 || rect.height < 2 {
1674		return;
1675	}
1676	let (_, _, bl, br, horizontal, _) = charset.border(Border::Round);
1677	let mut glyph = [0_u8; 4];
1678	let right = rect.x + rect.width - 1;
1679	let bottom = rect.y + rect.height - 1;
1680	frame.put(rect.x, bottom, bl.encode_utf8(&mut glyph), border);
1681	frame.put(right, bottom, br.encode_utf8(&mut glyph), border);
1682	for x in rect.x + 1..right {
1683		frame.put(x, bottom, horizontal.encode_utf8(&mut glyph), border);
1684	}
1685}
1686
1687fn draw_line(frame: &mut Frame, x: u16, y: u16, width: u16, spans: &[Span<'_>]) -> u16 {
1688	let right = x.saturating_add(width).min(frame.size().width);
1689	let mut column = x;
1690	for span in spans {
1691		column = frame.put_clipped(column, y, right.saturating_sub(column), span.text, span.style);
1692		if column >= right {
1693			break;
1694		}
1695	}
1696	column
1697}
1698
1699/// Flows `spans` grapheme-exact across the rect like a bare terminal,
1700/// preserving all whitespace and flagging each exactly-filled row boundary
1701/// soft so native selection copies the paragraph as one unbroken line.
1702/// Returns the rows used.
1703fn draw_flowed(frame: &mut Frame, rect: Rect, spans: &[Span<'_>]) -> u16 {
1704	if rect.width == 0 || rect.height == 0 {
1705		return 0;
1706	}
1707	let full_row = rect.x == 0 && rect.width == frame.size().width;
1708	let mut row = 0_u16;
1709	let mut column = 0_u16;
1710	let mut drew_anything = false;
1711
1712	for span in spans {
1713		for grapheme in xutf::graphemes_str(span.text) {
1714			let grapheme_width = visible_width(grapheme);
1715			if grapheme_width == 0 || grapheme_width > rect.width {
1716				continue;
1717			}
1718			if column.saturating_add(grapheme_width) > rect.width {
1719				// Only an exactly-filled row is byte-joinable by autowrap.
1720				if full_row && column == rect.width {
1721					frame.set_soft_wrap(rect.y.saturating_add(row));
1722				}
1723				row += 1;
1724				column = 0;
1725			}
1726			if row >= rect.height {
1727				return rect.height;
1728			}
1729			frame.put(rect.x + column, rect.y + row, grapheme, span.style);
1730			column += grapheme_width;
1731			drew_anything = true;
1732		}
1733	}
1734
1735	if drew_anything { row + 1 } else { 0 }
1736}
1737
1738fn visible_width(text: &str) -> u16 {
1739	u16::try_from(xutf::width_str(text)).unwrap_or(u16::MAX)
1740}
1741
1742/// Finds the first `<ref image=N/>` tag in submitted text: its byte range
1743/// plus the marker number `N`.
1744fn next_ref_tag(text: &str) -> Option<(usize, usize, usize)> {
1745	const HEAD: &str = "<ref image=";
1746	let mut from = 0;
1747	while let Some(at) = text[from..].find(HEAD) {
1748		let start = from + at;
1749		let body = &text[start + HEAD.len()..];
1750		let digits = body.bytes().take_while(u8::is_ascii_digit).count();
1751		if digits > 0 && body[digits..].starts_with("/>") {
1752			let marker = body[..digits].parse().unwrap_or(usize::MAX);
1753			return Some((start, start + HEAD.len() + digits + 2, marker));
1754		}
1755		from = start + HEAD.len();
1756	}
1757	None
1758}
1759
1760/// Splits one composer input row into base-styled text and attachment
1761/// chips, chip styling winning over any overlapping XML run.
1762///
1763/// Chips are located through the buffer's atomic ranges — like the XML
1764/// pass, styling happens at paint time, and typed lookalike text is never
1765/// recolored.
1766fn push_row_spans<'a>(
1767	editor: &Editor,
1768	row: &'a str,
1769	runs: &[SyntaxRun],
1770	spans: &mut SmallVec<Span<'a>, 16>,
1771) {
1772	let text = editor.text();
1773	let buffer_start = text.as_ptr() as usize;
1774	let row_start = (row.as_ptr() as usize).saturating_sub(buffer_start);
1775	let row_end = row_start + row.len();
1776	// Chip segments clipped to this row; style derives from the FULL atom
1777	// text, so a chip wrapped across rows keeps its color on every row.
1778	let mut chips: SmallVec<(usize, usize, Style), 4> = SmallVec::new();
1779	for (start, end) in editor.atom_ranges() {
1780		let from = start.max(row_start);
1781		let to = end.min(row_end);
1782		if from < to {
1783			chips.push((from - row_start, to - row_start, chip_style(&text[start..end])));
1784		}
1785	}
1786	// The style and extent of the base segment covering `at`: its XML run,
1787	// or plain text up to the next run.
1788	let base = |at: usize| {
1789		runs
1790			.iter()
1791			.find(|run| run.start <= at && at < run.end)
1792			.map_or_else(
1793				|| {
1794					let next = runs
1795						.iter()
1796						.map(|run| run.start)
1797						.filter(|start| *start > at)
1798						.min()
1799						.unwrap_or(row.len());
1800					(next, base_style())
1801				},
1802				|run| (run.end, run.style),
1803			)
1804	};
1805	fn emit<'a>(
1806		row: &'a str,
1807		base: &impl Fn(usize) -> (usize, Style),
1808		from: usize,
1809		to: usize,
1810		spans: &mut SmallVec<Span<'a>, 16>,
1811	) {
1812		let mut at = from;
1813		while at < to {
1814			let (run_end, style) = base(at);
1815			let end = run_end.min(to);
1816			spans.push(Span::new(&row[at..end], style));
1817			at = end;
1818		}
1819	}
1820	let mut at = 0;
1821	for (start, end, style) in chips {
1822		emit(row, &base, at, start, spans);
1823		spans.push(Span::new(&row[start..end], style));
1824		at = end;
1825	}
1826	emit(row, &base, at, row.len(), spans);
1827}
1828
1829/// Style for one atomic chip: a trailing `#N` selects the marker's
1830/// identity color; other atoms stay plain.
1831fn chip_style(chip: &str) -> Style {
1832	let Some(hash) = chip.rfind('#') else {
1833		return base_style();
1834	};
1835	let digits = &chip[hash + 1..];
1836	if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
1837		return base_style();
1838	}
1839	match digits.parse::<usize>() {
1840		Ok(marker) if marker > 0 => ink(attachment_color(marker)).bold(),
1841		_ => base_style(),
1842	}
1843}
1844
1845/// Paints one transcript line, rendering each `<ref image=N/>` tag as a
1846/// compact `<icon> #N` pill filled with the attachment's identity color.
1847fn draw_submission_text(
1848	frame: &mut Frame,
1849	x: u16,
1850	y: u16,
1851	width: u16,
1852	line: &str,
1853	charset: Charset,
1854) {
1855	let icon = charset.icon(Icon::Image);
1856	let mut chips: SmallVec<(usize, usize, String, usize), 4> = SmallVec::new();
1857	let mut base = 0;
1858	while let Some((start, end, marker)) = next_ref_tag(&line[base..]) {
1859		chips.push((base + start, base + end, format!("{icon} #{marker}"), marker));
1860		base += end;
1861	}
1862	let mut spans: SmallVec<Span<'_>, 8> = SmallVec::new();
1863	let mut at = 0;
1864	for (start, end, label, marker) in &chips {
1865		if *start > at {
1866			spans.push(Span::new(&line[at..*start], ink(TEXT)));
1867		}
1868		spans.push(Span::new(label, Style::new().fg(PANEL).bg(attachment_color(*marker)).bold()));
1869		at = *end;
1870	}
1871	if at < line.len() {
1872		spans.push(Span::new(&line[at..], ink(TEXT)));
1873	}
1874	draw_line(frame, x, y, width, &spans);
1875}
1876
1877/// Chrome outside a panel is transparent: no `bg`, so the terminal's own
1878/// background (and any image or blur behind it) shows through. Only the
1879/// panel boxes below opt into a fill.
1880const fn base_style() -> Style {
1881	Style::new().fg(TEXT)
1882}
1883
1884const fn panel_style() -> Style {
1885	Style::new().fg(TEXT).bg(PANEL)
1886}
1887
1888const fn ink(color: Color) -> Style {
1889	Style::new().fg(color)
1890}
1891
1892const fn panel_ink(color: Color) -> Style {
1893	Style::new().fg(color).bg(PANEL)
1894}
1895
1896const fn prose_style() -> Style {
1897	Style::new().fg(MUTED).italic()
1898}
Source

pub const fn underline(self) -> Self

Enables underlining.

Source

pub const fn underline_color(self, color: Color) -> Self

Sets the underline color (SGR 58); Color::Default leaves the terminal’s underline color untouched.

Source

pub const fn reverse(self) -> Self

Enables reverse video.

Source

pub const fn strikethrough(self) -> Self

Enables strikethrough.

Attaches a terminal hyperlink target to this style.

The URL is interned once and only its typed identity rides on rich-text runs and frame cells. Empty targets are ignored.

Source

pub const fn inherit(self, parent: Self) -> Self

CSS-like cascade: unset properties adopt the parent’s. A Color::Default foreground counts as unset and attribute flags OR together. The background never inherits — ancestor fills reach descendants through the paint underlay instead.

Source

pub const fn foreground_color(&self) -> Color

The foreground color, for callers deriving accents from a style.

Source

pub const fn background_color(&self) -> Color

The background color, for callers deciding whether to fill a region.

Trait Implementations§

Source§

impl Clone for Style

Source§

fn clone(&self) -> Style

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Style

Source§

impl Debug for Style

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Style

Source§

fn default() -> Style

Returns the “default value” for a type. Read more
Source§

impl Eq for Style

Source§

impl PartialEq for Style

Source§

fn eq(&self, other: &Style) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Style

Auto Trait Implementations§

§

impl Freeze for Style

§

impl RefUnwindSafe for Style

§

impl Send for Style

§

impl Sync for Style

§

impl Unpin for Style

§

impl UnsafeUnpin for Style

§

impl UnwindSafe for Style

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &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
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.