Watch
1
0
Fork
You've already forked souveraine
0

tuie: adapt to StyledText accessors and indexed Theme palette

Dep bump swapped StyledText's public .text field for as_ref/as_str
accessors and set_fg/set_bg/drop_end mutators; Theme now built from
(fg, bg, [16] indexed) instead of named slots. Mechanical port across
the widget set plus an ANSI-index mapping of the chat palette.
This commit is contained in:
Fimeg 2026-06-15 20:52:05 -04:00
commit 1734346e59
24 changed files with 117 additions and 137 deletions

2
Cargo.lock generated
View file

@ -10260,7 +10260,7 @@ dependencies = [
[[package]]
name = "tuie"
version = "0.1.5"
version = "0.2.0"
dependencies = [
"axis2d",
"base64-simd",

View file

@ -67,9 +67,9 @@ impl FieldGrid {
let mut rows = Vec::new();
let mut grid = Grid::new();
grid.set_columns(vec![
grid.set_cols(vec![
Track::auto().min(16), // label column, auto-sized with min 16
Track::grow(1), // control column, fills remaining space
Track::flex(1), // control column, fills remaining space
]);
grid.set_col_gap(1);

View file

@ -1060,10 +1060,10 @@ fn build_category_content(view: &SettingsView, focus: FocusCol, palette: &ChatPa
for group in CategoryGroup::all() {
// Group header — dim separator
let header = format!(" {} {}\n", group.icon(), group.label());
let h_start = content.text.len();
let h_start = content.as_ref().len();
content.push_str(&header);
content.style_range(h_start..content.text.len(), |s| {
s.fg = Some(group_dim);
content.style_range(h_start..content.as_ref().len(), |s| {
s.set_fg(Some(group_dim));
*s = s.bold();
});
@ -1073,15 +1073,15 @@ fn build_category_content(view: &SettingsView, focus: FocusCol, palette: &ChatPa
let selected = i == view.category_idx;
let marker = if selected { "" } else { " " };
let line = format!(" {marker} {}\n", cat.label());
let start = content.text.len();
let start = content.as_ref().len();
content.push_str(&line);
let color = if selected {
if focus == FocusCol::Categories { primary } else { dim }
} else {
dim
};
content.style_range(start..content.text.len(), |s| {
s.fg = Some(color);
content.style_range(start..content.as_ref().len(), |s| {
s.set_fg(Some(color));
if selected {
*s = s.bold();
}

View file

@ -306,8 +306,8 @@ fn build_portrait_pane(palette: &ChatPalette, status: &AgentStatus) -> Box<Pane>
Pane::new()
.flex(1)
.min_height(0)
.x_place(Place::Middle)
.y_place(Place::Middle)
.x_place(Place::Center)
.y_place(Place::Center)
.children([portrait]) as Box<dyn Widget>,
Text::new().content(name_line).center(),
])
@ -376,8 +376,8 @@ fn make_card(label: &str, value: &str, color: Color) -> Box<Pane> {
Pane::new()
.flex(1)
.min_height(0)
.x_place(Place::Middle)
.y_place(Place::Middle)
.x_place(Place::Center)
.y_place(Place::Center)
.children([Text::new().content(content).center()]),
])
}

View file

@ -27,16 +27,25 @@ use crate::ui::chat::ChatPalette;
/// | `compaction` | `red` | urgency, warning |
/// | `reflection` | `blue` | calm, reflective |
pub fn chat_palette_to_theme(palette: &ChatPalette) -> Theme {
Theme {
bg: to_tuie_rgb(palette.bg),
fg: to_tuie_rgb(palette.agent_dim),
cyan: to_tuie_rgb(palette.user_accent),
green: to_tuie_rgb(palette.tool_accent),
yellow: to_tuie_rgb(palette.agent_primary),
magenta: to_tuie_rgb(palette.surfacing),
red: to_tuie_rgb(palette.compaction),
blue: to_tuie_rgb(palette.reflection),
let fg = to_tuie_rgb(palette.agent_dim);
let bg = to_tuie_rgb(palette.bg);
let default = to_tuie_rgb(ratatui::style::Color::Rgb(128, 128, 128));
let mut indexed = [default; 16];
// Map palette slots to ANSI color indices.
indexed[1] = to_tuie_rgb(palette.compaction); // red
indexed[2] = to_tuie_rgb(palette.tool_accent); // green
indexed[3] = to_tuie_rgb(palette.agent_primary); // yellow
indexed[4] = to_tuie_rgb(palette.reflection); // blue
indexed[5] = to_tuie_rgb(palette.surfacing); // magenta
indexed[6] = to_tuie_rgb(palette.user_accent); // cyan
// Fill unused slots with reasonable dark variants.
indexed[0] = to_tuie_rgb(palette.bg); // black → bg
indexed[7] = to_tuie_rgb(palette.agent_primary); // white → primary
indexed[8] = to_tuie_rgb(ratatui::style::Color::Rgb(64, 64, 64)); // bright black
for i in 9..16 {
indexed[i] = indexed[i - 8]; // bright variants = dim variants
}
Theme::new(fg, bg, indexed)
}
/// Apply an atmosphere to tuie's global palette.

View file

@ -80,10 +80,10 @@ impl StyleEntry {
fn to_style(&self) -> Style {
let mut s = Style::new();
if let Some(fg) = self.fg {
s.fg = Some(fg);
s.set_fg(Some(fg));
}
if let Some(bg) = self.bg {
s.bg = Some(bg);
s.set_bg(Some(bg));
}
if self.bold { s.set_bold(true); }
if self.italic { s.set_italic(true); }
@ -159,13 +159,13 @@ impl<'a> Renderer<'a> {
fn flush(&mut self) {
// trim trailing newlines
let text_bytes = self.out.text.as_bytes();
let text_bytes = self.out.as_ref().as_bytes();
let mut trailing = 0;
for &b in text_bytes.iter().rev() {
if b == b'\n' { trailing += 1; } else { break; }
}
if trailing > 1 {
self.out.trim_right(trailing - 1);
self.out.drop_end(trailing - 1);
}
}
@ -237,7 +237,7 @@ impl<'a> Renderer<'a> {
fn start(&mut self, tag: Tag<'_>) {
match tag {
Tag::Heading { level, .. } => {
if !self.out.text.is_empty() && !self.out.text.ends_with('\n') {
if !self.out.as_ref().is_empty() && !self.out.as_ref().ends_with('\n') {
self.newline();
}
self.newline();
@ -261,8 +261,8 @@ impl<'a> Renderer<'a> {
});
}
Tag::Paragraph => {
if !self.out.text.is_empty() && !self.out.text.ends_with("\n\n") {
if !self.out.text.ends_with('\n') {
if !self.out.as_ref().is_empty() && !self.out.as_ref().ends_with("\n\n") {
if !self.out.as_ref().ends_with('\n') {
self.newline();
}
self.newline();
@ -317,7 +317,7 @@ impl<'a> Renderer<'a> {
self.code_lang = Some(dest_url.into_string());
}
Tag::CodeBlock(kind) => {
if !self.out.text.is_empty() && !self.out.text.ends_with('\n') {
if !self.out.as_ref().is_empty() && !self.out.as_ref().ends_with('\n') {
self.newline();
}
self.in_code_block = true;
@ -334,7 +334,7 @@ impl<'a> Renderer<'a> {
self.at_line_start = false;
}
Tag::List(start) => {
if !self.out.text.is_empty() && !self.out.text.ends_with('\n') {
if !self.out.as_ref().is_empty() && !self.out.as_ref().ends_with('\n') {
self.newline();
}
self.list_stack.push(match start {
@ -343,7 +343,7 @@ impl<'a> Renderer<'a> {
});
}
Tag::Item => {
if !self.out.text.is_empty() && !self.out.text.ends_with('\n') {
if !self.out.as_ref().is_empty() && !self.out.as_ref().ends_with('\n') {
self.newline();
}
let bullet = match self.list_stack.last_mut() {
@ -358,7 +358,7 @@ impl<'a> Renderer<'a> {
self.pending_bullet = Some(bullet);
}
Tag::BlockQuote(_) => {
if !self.out.text.is_empty() && !self.out.text.ends_with('\n') {
if !self.out.as_ref().is_empty() && !self.out.as_ref().ends_with('\n') {
self.newline();
}
self.quote_depth += 1;
@ -374,7 +374,7 @@ impl<'a> Renderer<'a> {
self.newline();
}
TagEnd::Paragraph => {
if !self.out.text.ends_with('\n') {
if !self.out.as_ref().ends_with('\n') {
self.newline();
}
}
@ -390,7 +390,7 @@ impl<'a> Renderer<'a> {
}
}
TagEnd::CodeBlock => {
if !self.out.text.ends_with('\n') {
if !self.out.as_ref().ends_with('\n') {
self.newline();
}
self.in_code_block = false;
@ -434,40 +434,40 @@ mod tests {
#[test]
fn renders_plain_paragraph() {
let out = render("hello world", &test_palette());
assert!(out.text.contains("hello world"));
assert!(out.as_ref().contains("hello world"));
}
#[test]
fn renders_inline_code() {
let out = render("call `foo()` then", &test_palette());
assert!(out.text.contains("`foo()`"));
assert!(out.as_ref().contains("`foo()`"));
}
#[test]
fn renders_fenced_code_block() {
let out = render("```rust\nfn main() {}\n```", &test_palette());
assert!(out.text.contains("rust"));
assert!(out.text.contains("fn main()"));
assert!(out.as_ref().contains("rust"));
assert!(out.as_ref().contains("fn main()"));
}
#[test]
fn renders_heading() {
let out = render("# Big\n\nbody", &test_palette());
assert!(out.text.contains("# Big"));
assert!(out.text.contains("body"));
assert!(out.as_ref().contains("# Big"));
assert!(out.as_ref().contains("body"));
}
#[test]
fn renders_bullet_list() {
let out = render("- one\n- two", &test_palette());
assert!(out.text.contains("\u{2022} one"));
assert!(out.text.contains("\u{2022} two"));
assert!(out.as_ref().contains("\u{2022} one"));
assert!(out.as_ref().contains("\u{2022} two"));
}
#[test]
fn renders_blockquote() {
let out = render("> quoted text", &test_palette());
assert!(out.text.contains("quoted text"));
assert!(out.text.contains("\u{258c}"));
assert!(out.as_ref().contains("quoted text"));
assert!(out.as_ref().contains("\u{258c}"));
}
}

View file

@ -131,8 +131,8 @@ impl DelegateWidget for Accordion {
};
match &event.chord {
chord!(LeftClick) => {
let clicked_header = event.mouse_pos.y >= 0
&& event.mouse_pos.y < Self::HEADER_HEIGHT as i32;
let clicked_header = event.pos.y >= 0.0
&& event.pos.y < Self::HEADER_HEIGHT as f32;
if clicked_header {
if self.expanded {
self.close();
@ -176,7 +176,7 @@ impl Accordion {
.gap(1)
.style(Style::new().bg(Color::grey256(5)))
.horizontal_padding(1)
.y_place(Place::Middle)
.y_place(Place::Center)
.children([
Text::new().content(title.to_string()).flex(1).id(&mut title_id),
Text::new().content(">").id(&mut chevron_id),

View file

@ -27,7 +27,7 @@ impl DelegateWidget for Button {
chord!(LeftRelease) => {
let size = self.get_rect_size();
let released_inside = Axis2D::all(|axis| {
event.mouse_pos[axis] >= 0 && event.mouse_pos[axis] < size[axis] as i32
event.pos[axis] >= 0.0 && event.pos[axis] < size[axis] as f32
});
if released_inside {
tuie::emit(self.get_id(), ClickEvent);

View file

@ -151,7 +151,7 @@ impl ChatBubble {
.map(|f| f.chars().count() + 2)
.unwrap_or(0);
let body_w = if let Some(styled) = &self.body_styled {
styled.text.split('\n')
styled.as_str().split('\n')
.map(|l| unicode_display_width(l))
.max()
.unwrap_or(0)
@ -196,28 +196,28 @@ impl ChatBubble {
let left_dash = "\u{2500}".repeat(dashes / 2);
let right_dash = "\u{2500}".repeat(dashes - dashes / 2);
let top_line = format!("{pad}\u{256d}{left_dash}{title_text}{right_dash}\u{256e}\n");
let s = content.text.len();
let s = content.as_ref().len();
content.push_str(&top_line);
content.style_range(s..content.text.len(), |style| *style = border);
content.style_range(s..content.as_ref().len(), |style| *style = border);
if let Some(styled) = &self.body_styled {
let body_text = &styled.text;
let body_text = styled.as_str();
for line_str in body_text.split('\n') {
let line_byte_start = line_str.as_ptr() as usize - body_text.as_ptr() as usize;
for sub in wrap_sub_lines(line_str, inner) {
let sub_width = unicode_display_width(sub.text);
let inner_pad = inner.saturating_sub(sub_width);
let left = format!("{pad}\u{2502} ");
let s = content.text.len();
let s = content.as_ref().len();
content.push_str(&left);
content.style_range(s..content.text.len(), |style| *style = border);
content.style_range(s..content.as_ref().len(), |style| *style = border);
let abs_start = line_byte_start + sub.byte_offset;
let abs_end = abs_start + sub.text.len();
push_styled_slice(&mut content, styled, abs_start, abs_end);
let right = format!("{} \u{2502}\n", " ".repeat(inner_pad));
let s = content.text.len();
let s = content.as_ref().len();
content.push_str(&right);
content.style_range(s..content.text.len(), |style| *style = border);
content.style_range(s..content.as_ref().len(), |style| *style = border);
}
}
} else {
@ -229,9 +229,9 @@ impl ChatBubble {
"{pad}\u{2502} {line}{} \u{2502}\n",
" ".repeat(inner_pad),
);
let s = content.text.len();
let s = content.as_ref().len();
content.push_str(&body_line);
content.style_range(s..content.text.len(), |style| *style = border);
content.style_range(s..content.as_ref().len(), |style| *style = border);
}
}
@ -249,9 +249,9 @@ impl ChatBubble {
format!("{pad}\u{2570}{bottom_dashes}\u{256f}")
}
};
let s = content.text.len();
let s = content.as_ref().len();
content.push_str(&bottom_line);
content.style_range(s..content.text.len(), |style| *style = border);
content.style_range(s..content.as_ref().len(), |style| *style = border);
self.text.set_content(content);
self.text.dirty_layout();
@ -259,7 +259,7 @@ impl ChatBubble {
}
fn unicode_display_width(s: &str) -> usize {
tuie::terminal_display_width(s)
tuie::display_width(s)
}
/// Copy a byte-range slice from a `StyledString` into `out`, preserving span styles.
@ -267,38 +267,8 @@ fn push_styled_slice(out: &mut StyledString, src: &StyledString, start: usize, e
if start >= end {
return;
}
let text_slice = &src.text[start..end];
if src.spans.is_empty() {
out.push_str(text_slice);
return;
}
// Walk spans to find which ones overlap [start..end]
let mut pos = 0usize;
let mut slice_offset = 0usize;
for span in &src.spans {
let span_end = pos + span.len;
if span_end <= start {
pos = span_end;
continue;
}
if pos >= end {
break;
}
let overlap_start = start.max(pos);
let overlap_end = end.min(span_end);
let chunk = &src.text[overlap_start..overlap_end];
if !chunk.is_empty() {
out.push_span(StyledStr { text: chunk, style: span.style });
}
slice_offset += overlap_end - overlap_start;
pos = span_end;
}
// If spans didn't cover the full range (unstyled tail), push remainder
if slice_offset < (end - start) {
let remainder = &text_slice[slice_offset..];
if !remainder.is_empty() {
out.push_str(remainder);
}
for (chunk, style) in src.iter_chunks(start..end) {
out.push_span(StyledStr::new(chunk).style(style));
}
}

View file

@ -63,7 +63,7 @@ impl DelegateWidget for Checkbox {
chord!(LeftRelease) => {
let size = self.get_rect_size();
if Axis2D::all(|a| {
event.mouse_pos[a] >= 0 && event.mouse_pos[a] < size[a] as i32
event.pos[a] >= 0.0 && event.pos[a] < size[a] as f32
}) {
self.toggle();
}

View file

@ -99,7 +99,7 @@ impl Cockpit {
let footer = Pane::new()
.horizontal()
.height(1)
.y_place(Place::Middle)
.y_place(Place::Center)
.gap(1)
.horizontal_padding(1)
.children([
@ -294,10 +294,10 @@ fn build_subconscious_text(entries: &[ChatCockpitEntry], palette: &ChatPalette)
let prefix = entry_kind_prefix(entry.kind);
let line = format!(" {} {}\n", prefix, entry.text);
let start = content.text.len();
let start = content.as_ref().len();
content.push_str(&line);
content.style_range(start..content.text.len(), |s| {
s.fg = Some(color);
content.style_range(start..content.as_ref().len(), |s| {
s.set_fg(Some(color));
});
}

View file

@ -84,7 +84,7 @@ impl InputBindings<Text> for NumericBindings {
if let Ok(v) = simulated.parse::<i32>() {
if v < self.min || v > self.max {
let clamped = v.clamp(self.min, self.max).to_string();
state.replace_all(text, &clamped);
state.replace_range(text, 0..text.len(), &clamped);
queue.next();
return InputResult::Handled;
}
@ -271,8 +271,8 @@ impl DelegateWidget for Counter {
match &event.chord {
chord!(LeftClick) => {
tuie::focus_widget(self.input_id);
let mouse_pos = event.mouse_pos;
if let Some(which) = self.hit_test_buttons(mouse_pos) {
let mouse_pos = event.pos;
if let Some(which) = self.hit_test_buttons(mouse_pos.map(|v| v as i32)) {
queue.next();
self.set_pressed(Some(which));
return InputResult::Handled;
@ -280,10 +280,10 @@ impl DelegateWidget for Counter {
InputResult::Rejected
}
chord!(LeftRelease) => {
let mouse_pos = event.mouse_pos;
let mouse_pos = event.pos;
queue.next();
if let Some(which) = self.pressed {
if self.hit_test_buttons(mouse_pos) == Some(which) {
if self.hit_test_buttons(mouse_pos.map(|v| v as i32)) == Some(which) {
self.adjust(which);
self.select_all_input();
}
@ -372,7 +372,7 @@ impl Counter {
.bindings(NumericBindings::new)
.content("0")
.overflow(TextOverflow::VISIBLE)
.align(Align::Middle)
.align(Align::Center)
.horizontal_margin(0)
.id(&mut input_id),
Text::new().content(" >").id(&mut plus_id),

View file

@ -66,7 +66,7 @@ impl DelegateWidget for Dropdown {
Trigger::MouseUp(MouseButton::Left) => {
let size = self.get_rect_size();
let inside = Axis2D::all(|a| {
event.mouse_pos[a] >= 0 && event.mouse_pos[a] < size[a] as i32
event.pos[a] >= 0.0 && event.pos[a] < size[a] as f32
});
if inside {
self.open_menu();
@ -141,7 +141,7 @@ impl Dropdown {
tuie::open_popup(
Popup::new(host as Box<dyn Widget>)
.placement(Placement::side(Direction2D::Down, Sign::Positive, Align::Start))
.dismissible(true),
.dismissible(),
);
}
}

View file

@ -20,21 +20,22 @@ impl FlatButton {
let mut style = base;
match self.state {
WidgetState::None | WidgetState::Hover => {
if style.bg.is_none() {
style.bg = Some(self.bg);
if style.get_bg().is_none() {
style.set_bg(Some(self.bg));
}
}
WidgetState::Focused | WidgetState::FocusedHover => {
style.bg = None;
style.fg = Some(theme::get_accent_color());
style.set_bg(None);
style.set_fg(Some(theme::get_accent_color()));
style.set_reverse(true);
}
WidgetState::Active => {
style.bg = None;
style.fg = Some(theme::get_accent_color());
style.set_bg(None);
style.set_fg(Some(theme::get_accent_color()));
style.set_reverse(true);
style.set_blend(Some(75));
}
_ => {}
}
self.pane.set_style(style);
}
@ -57,8 +58,8 @@ impl DelegateWidget for FlatButton {
chord!(LeftRelease) => {
let size = self.get_rect_size();
let inside = Axis2D::all(|a| {
event.mouse_pos[a] >= 0
&& event.mouse_pos[a] < size[a] as i32
event.pos[a] >= 0.0
&& event.pos[a] < size[a] as f32
});
if inside {
tuie::emit(self.get_id(), ClickEvent);
@ -89,7 +90,7 @@ impl FlatButton {
pub(crate) fn new() -> Box<Self> {
Box::new(Self {
pane: Pane::new(),
bg: border::config::get().style.bg.unwrap_or(Color::grey256(5)),
bg: border::config::get().style.get_bg().unwrap_or(Color::grey256(5)),
state: WidgetState::None,
base_style: None,
})

View file

@ -15,7 +15,7 @@ pub(crate) struct FocusPane {
impl FocusPane {
fn refresh_border(&mut self) {
let cfg = border::config::get();
let focused = tuie::runtime::is_focus_chain(self.pane.get_id());
let focused = tuie::runtime::in_focus_chain(self.pane.get_id());
if focused {
let style = self
.selected_border_style

View file

@ -75,7 +75,7 @@ impl Widget for Link {
let base = self.layout.style;
let style = if matches!(self.state.get(), WidgetState::Active) {
theme::get_accent().apply(base).underline(UnderlineType::Single)
} else if self.is_focus_chain() {
} else if self.in_focus_chain() {
theme::get_accent().bold().apply(base).underline(UnderlineType::Single)
} else {
base.underline(UnderlineType::Single)
@ -98,7 +98,7 @@ impl Widget for Link {
chord!(LeftRelease) => {
let size = self.get_rect_size();
if Axis2D::all(|a| {
event.mouse_pos[a] >= 0 && event.mouse_pos[a] < size[a] as i32
event.pos[a] >= 0.0 && event.pos[a] < size[a] as f32
}) {
tuie::focus_widget(self.get_id());
self.open_url();

View file

@ -69,14 +69,14 @@ impl PhaseBar {
};
content.push_str(&line);
let len = content.text.len();
let len = content.as_ref().len();
content.style_range(0..len, |s| *s = style);
if queued > 0 {
let extra = format!(" · {} queued", queued);
content.push_str(&extra);
let q_start = len;
content.style_range(q_start..content.text.len(), |s| {
content.style_range(q_start..content.as_ref().len(), |s| {
*s = dim_style.italic()
});
}

View file

@ -89,7 +89,7 @@ impl Widget for PointPicker {
let size = self.size.get();
let selected = self.selected.get();
let pressed = self.pressed.get();
let focused = self.is_focus_chain();
let focused = self.in_focus_chain();
for row in 0..size.y {
for col in 0..size.x {
@ -155,7 +155,7 @@ impl Widget for PointPicker {
}
}
chord!(LeftClick) => {
if let Some(cell) = self.hit_cell(event.mouse_pos) {
if let Some(cell) = self.hit_cell(event.pos.map(|v| v as i32)) {
tuie::focus_widget(self.get_id());
self.set_pressed(Some(cell));
}
@ -163,7 +163,7 @@ impl Widget for PointPicker {
chord!(LeftRelease) => {
let pressed = self.pressed.get();
self.set_pressed(None);
if let Some(cell) = self.hit_cell(event.mouse_pos) {
if let Some(cell) = self.hit_cell(event.pos.map(|v| v as i32)) {
if pressed == Some(cell) {
self.select_cell(cell);
}

View file

@ -30,7 +30,7 @@ impl Portrait {
// with cover-style scaling (crops to fill, no letterboxing).
let mut img = Image::new(source);
img.set_fill(true);
img.flex(1).x_align(FlexAlign::Middle)
img.flex(1).x_align(FlexAlign::Center)
}
None => ascii_portrait(name, color),
};

View file

@ -85,7 +85,7 @@ impl Widget for RadioGroup {
let pressed = self.pressed.get();
let base = self.layout.style;
let accent = theme::get_accent_color();
let marker_style = if self.is_focus_chain() {
let marker_style = if self.in_focus_chain() {
base.fg(accent).bold()
} else {
base.bold()
@ -138,7 +138,7 @@ impl Widget for RadioGroup {
}
}
chord!(LeftClick) => {
if let Some(i) = self.hit_option(event.mouse_pos) {
if let Some(i) = self.hit_option(event.pos.map(|v| v as i32)) {
tuie::focus_widget(self.get_id());
self.set_pressed(Some(i));
}
@ -146,7 +146,7 @@ impl Widget for RadioGroup {
chord!(LeftRelease) => {
let pressed = self.pressed.get();
self.set_pressed(None);
if let Some(i) = self.hit_option(event.mouse_pos) {
if let Some(i) = self.hit_option(event.pos.map(|v| v as i32)) {
if pressed == Some(i) {
self.select_index(i);
}

View file

@ -108,7 +108,7 @@ impl Widget for Responsive {
fn layout_measure(&self, allocated: Vec2<u16>) -> Vec2<u16> {
let wide = allocated[Axis2D::X] >= self.breakpoint;
let child: &dyn Widget = if wide { &*self.wide } else { &*self.narrow };
flow_child_measure(child, allocated);
measure_child(child, allocated);
allocated
}

View file

@ -104,7 +104,7 @@ impl Widget for SegmentedControl {
let pressed = self.pressed.get();
let base = self.layout.style;
let accent = theme::get_accent_color();
let selected_style = if self.is_focus_chain() {
let selected_style = if self.in_focus_chain() {
Style::new().fg(Color::BLACK).bg(accent).bold()
} else {
base.reverse().bold()
@ -160,7 +160,7 @@ impl Widget for SegmentedControl {
}
}
chord!(LeftClick) => {
if let Some(i) = self.hit_segment(event.mouse_pos) {
if let Some(i) = self.hit_segment(event.pos.map(|v| v as i32)) {
if !self.is_disabled(i) {
tuie::focus_widget(self.get_id());
self.set_pressed(Some(i));
@ -170,7 +170,7 @@ impl Widget for SegmentedControl {
chord!(LeftRelease) => {
let pressed = self.pressed.get();
self.set_pressed(None);
if let Some(i) = self.hit_segment(event.mouse_pos) {
if let Some(i) = self.hit_segment(event.pos.map(|v| v as i32)) {
if pressed == Some(i) {
self.select_index(i);
}

View file

@ -86,8 +86,8 @@ impl Widget for Slider {
ctx.set_style(base);
ctx.clear();
let focused = self.is_focus_chain();
let fill_color = if focused { self.accent } else { base.fg.unwrap_or(self.accent) };
let focused = self.in_focus_chain();
let fill_color = if focused { self.accent } else { base.get_fg().unwrap_or(self.accent) };
let eighths = (self.fraction() * TRACK_WIDTH as f32 * 8.0).round() as u32;
let full = (eighths / 8) as u16;
let remainder = (eighths % 8) as usize;
@ -127,11 +127,11 @@ impl Widget for Slider {
Trigger::MouseDown(MouseButton::Left) => {
tuie::focus_widget(self.get_id());
self.dragging.set(true);
self.set_value(self.value_at_x(event.mouse_pos.x));
self.set_value(self.value_at_x(event.pos.x as i32));
}
Trigger::MouseDrag(MouseButton::Left) => {
if self.dragging.get() {
self.set_value(self.value_at_x(event.mouse_pos.x));
self.set_value(self.value_at_x(event.pos.x as i32));
}
}
Trigger::MouseUp(MouseButton::Left) => {

View file

@ -197,7 +197,7 @@ impl ToolCard {
// For now, render as compact with a "[expanded]" marker.
let mut content = StyledString::new();
content.push_str(&format!(" [expanded] {} (r{})", self.name, self.round));
let len = content.text.len();
let len = content.as_ref().len();
content.style_range(0..len, |s| *s = self.name_style);
self.text.set_content(content);
}