Another project
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(ui): theme scoping, token-boundary lint

Lewis: May this revision serve well! <lu5a@proton.me>

+386 -237
+4
clippy.toml
··· 1 + disallowed-methods = [ 2 + { path = "palette::rgb::Rgb::new", reason = "construct Color via theme tokens; raw palette colour bypasses the bone-ui::theme boundary" }, 3 + { path = "palette::Oklch::new", reason = "construct Color via theme tokens; raw palette colour bypasses the bone-ui::theme boundary" }, 4 + ]
+155 -2
crates/bone-ui/src/frame.rs
··· 56 56 } 57 57 58 58 pub struct FrameCtx<'a> { 59 - pub theme: Arc<Theme>, 59 + theme: Arc<Theme>, 60 60 pub input: &'a mut InputSnapshot, 61 61 pub focus: &'a mut FocusManager, 62 62 pub hotkeys: &'a HotkeyTable, ··· 90 90 } 91 91 92 92 #[must_use] 93 + pub fn theme(&self) -> &Theme { 94 + &self.theme 95 + } 96 + 97 + #[must_use] 93 98 pub fn is_focused(&self, id: WidgetId) -> bool { 94 99 self.focus.focused() == Some(id) 95 100 } ··· 116 121 interaction 117 122 } 118 123 124 + pub fn theme_scope<R>( 125 + &mut self, 126 + modify: impl FnOnce(&Theme) -> Theme, 127 + body: impl FnOnce(&mut FrameCtx<'a>) -> R, 128 + ) -> R { 129 + let outer = Arc::clone(&self.theme); 130 + self.theme = Arc::new(modify(&self.theme)); 131 + let result = body(self); 132 + self.theme = outer; 133 + result 134 + } 135 + 119 136 pub fn dispatch_hotkeys(&mut self, scopes: &HotkeyScopes) -> Vec<ActionId> { 120 137 let table = self.hotkeys; 121 138 let pending = std::mem::take(&mut self.input.keys_pressed); ··· 158 175 }; 159 176 use crate::layout::{LayoutPos, LayoutPx, LayoutRect, LayoutSize}; 160 177 use crate::strings::StringTable; 161 - use crate::theme::Theme; 178 + use crate::theme::{Theme, ThemeMode}; 162 179 use crate::widget_id::{WidgetId, WidgetKey}; 163 180 164 181 fn global_scope() -> HotkeyScopes { ··· 346 363 Some(id("btn")), 347 364 "click on focusable widget auto-focuses next frame", 348 365 ); 366 + } 367 + 368 + #[test] 369 + fn theme_scope_swaps_theme_for_body_and_restores_after() { 370 + let mut focus = FocusManager::new(); 371 + let hotkeys = HotkeyTable::new(); 372 + let mut hits = HitFrame::new(); 373 + let prev = HitState::new(); 374 + let mut input = InputSnapshot::idle(FrameInstant::ZERO); 375 + let mut frame = FrameCtx::new( 376 + Arc::new(Theme::light()), 377 + &mut input, 378 + &mut focus, 379 + &hotkeys, 380 + StringTable::empty(), 381 + &mut hits, 382 + &prev, 383 + ); 384 + assert_eq!(frame.theme().mode, ThemeMode::Light); 385 + let inner_mode = frame.theme_scope(|_| Theme::dark(), |frame| frame.theme().mode); 386 + assert_eq!(inner_mode, ThemeMode::Dark); 387 + assert_eq!(frame.theme().mode, ThemeMode::Light); 388 + } 389 + 390 + #[test] 391 + fn nested_theme_scopes_unwind_lifo() { 392 + let mut focus = FocusManager::new(); 393 + let hotkeys = HotkeyTable::new(); 394 + let mut hits = HitFrame::new(); 395 + let prev = HitState::new(); 396 + let mut input = InputSnapshot::idle(FrameInstant::ZERO); 397 + let mut frame = FrameCtx::new( 398 + Arc::new(Theme::light()), 399 + &mut input, 400 + &mut focus, 401 + &hotkeys, 402 + StringTable::empty(), 403 + &mut hits, 404 + &prev, 405 + ); 406 + let modes = frame.theme_scope( 407 + |_| Theme::dark(), 408 + |frame| { 409 + let outer_mode = frame.theme().mode; 410 + let inner_mode = 411 + frame.theme_scope(|_| Theme::light(), |frame| frame.theme().mode); 412 + let after_inner = frame.theme().mode; 413 + (outer_mode, inner_mode, after_inner) 414 + }, 415 + ); 416 + assert_eq!( 417 + modes, 418 + (ThemeMode::Dark, ThemeMode::Light, ThemeMode::Dark) 419 + ); 420 + assert_eq!(frame.theme().mode, ThemeMode::Light); 421 + } 422 + 423 + #[test] 424 + fn theme_scope_observes_modified_accent_via_clone() { 425 + let mut focus = FocusManager::new(); 426 + let hotkeys = HotkeyTable::new(); 427 + let mut hits = HitFrame::new(); 428 + let prev = HitState::new(); 429 + let mut input = InputSnapshot::idle(FrameInstant::ZERO); 430 + let mut frame = FrameCtx::new( 431 + Arc::new(Theme::light()), 432 + &mut input, 433 + &mut focus, 434 + &hotkeys, 435 + StringTable::empty(), 436 + &mut hits, 437 + &prev, 438 + ); 439 + let outer_accent = frame.theme().colors.accent_solid(); 440 + let outer_ring = frame.theme().colors.focus_ring(); 441 + let (inner_accent, inner_ring) = frame.theme_scope( 442 + |t| { 443 + let mut next = t.clone(); 444 + next.colors.accent = t.colors.danger; 445 + next 446 + }, 447 + |frame| { 448 + ( 449 + frame.theme().colors.accent_solid(), 450 + frame.theme().colors.focus_ring(), 451 + ) 452 + }, 453 + ); 454 + assert_ne!(inner_accent, outer_accent); 455 + assert_eq!(inner_ring, inner_accent); 456 + assert_eq!(frame.theme().colors.accent_solid(), outer_accent); 457 + assert_eq!(frame.theme().colors.focus_ring(), outer_ring); 458 + } 459 + 460 + #[test] 461 + fn hot_swap_between_frames_rebinds_token_reads() { 462 + let mut focus = FocusManager::new(); 463 + let hotkeys = HotkeyTable::new(); 464 + let strings = StringTable::empty(); 465 + let prev = HitState::new(); 466 + let read_tokens = |theme: Arc<Theme>, 467 + focus: &mut FocusManager, 468 + hits: &mut HitFrame, 469 + input: &mut InputSnapshot| { 470 + let frame = FrameCtx::new(theme, input, focus, &hotkeys, strings, hits, &prev); 471 + ( 472 + frame.theme().mode, 473 + frame.theme().colors.text_primary(), 474 + frame.theme().colors.surface(crate::theme::SurfaceLevel::L0), 475 + ) 476 + }; 477 + 478 + let mut hits_a = HitFrame::new(); 479 + let mut input_a = InputSnapshot::idle(FrameInstant::ZERO); 480 + let (mode_a, text_a, surface_a) = read_tokens( 481 + Arc::new(Theme::light()), 482 + &mut focus, 483 + &mut hits_a, 484 + &mut input_a, 485 + ); 486 + 487 + let mut hits_b = HitFrame::new(); 488 + let mut input_b = InputSnapshot::idle(FrameInstant::ZERO); 489 + let (mode_b, text_b, surface_b) = read_tokens( 490 + Arc::new(Theme::dark()), 491 + &mut focus, 492 + &mut hits_b, 493 + &mut input_b, 494 + ); 495 + 496 + assert_eq!(mode_a, ThemeMode::Light); 497 + assert_eq!(mode_b, ThemeMode::Dark); 498 + assert_ne!(text_a, text_b); 499 + assert_ne!(surface_a, surface_b); 500 + assert!(surface_a.relative_luminance() > surface_b.relative_luminance()); 501 + assert!(text_a.relative_luminance() < text_b.relative_luminance()); 349 502 } 350 503 }
+27 -25
crates/bone-ui/src/theme/color.rs
··· 66 66 pub const TRANSPARENT: Self = Self([0.0, 0.0, 0.0, 0.0]); 67 67 68 68 #[must_use] 69 - pub(in crate::theme) const fn from_linear_rgba_premul(r: f32, g: f32, b: f32, a: f32) -> Self { 70 - Self([r, g, b, a]) 71 - } 72 - 73 - #[must_use] 74 69 pub(in crate::theme) fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { 75 70 Self([r * a, g * a, b * a, a]) 76 71 } 77 72 78 73 #[must_use] 79 74 pub(in crate::theme) fn from_srgb_u8(r: u8, g: u8, b: u8) -> Self { 75 + #[allow(clippy::disallowed_methods)] 80 76 let lin: LinSrgb<f32> = Srgb::new( 81 77 f32::from(r) / 255.0, 82 78 f32::from(g) / 255.0, ··· 142 138 let b = self.0[2] * inv; 143 139 0.2126 * r + 0.7152 * g + 0.0722 * b 144 140 } 145 - 146 - #[must_use] 147 - pub fn on_surface(self) -> Self { 148 - if self.relative_luminance() > 0.179 { 149 - Self::from_linear_rgba_premul(0.04, 0.04, 0.04, 1.0) 150 - } else { 151 - Self::from_linear_rgba_premul(1.0, 1.0, 1.0, 1.0) 152 - } 153 - } 154 141 } 142 + 143 + const CONTRAST_LUMINANCE_THRESHOLD: f32 = 0.179; 155 144 156 145 impl core::fmt::Debug for Color { 157 146 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { ··· 177 166 const GAMUT_BISECT_STEPS: u32 = 24; 178 167 179 168 fn oklch_to_linsrgb_raw(l: f32, c: f32, h_degrees: f32) -> [f32; 3] { 169 + #[allow(clippy::disallowed_methods)] 180 170 let lin = LinSrgb::<f32>::from_color_unclamped(Oklch::<f32>::new(l, c, h_degrees)); 181 171 [lin.red, lin.green, lin.blue] 182 172 } ··· 312 302 pub success: Scale12, 313 303 pub warning: Scale12, 314 304 pub danger: Scale12, 315 - pub focus_ring: Color, 316 305 } 317 306 318 307 impl Colors { ··· 360 349 pub fn danger_solid(&self) -> Color { 361 350 self.danger.step(Step12::SOLID) 362 351 } 352 + 353 + #[must_use] 354 + pub fn focus_ring(&self) -> Color { 355 + self.accent_solid() 356 + } 357 + 358 + #[must_use] 359 + pub fn contrast_text(&self, surface: Color) -> Color { 360 + if surface.relative_luminance() > CONTRAST_LUMINANCE_THRESHOLD { 361 + Color::from_linear_rgba(0.04, 0.04, 0.04, 1.0) 362 + } else { 363 + Color::from_linear_rgba(1.0, 1.0, 1.0, 1.0) 364 + } 365 + } 363 366 } 364 367 365 368 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] ··· 428 431 } 429 432 430 433 fn build_colors(lightness: &[f32; 12]) -> Colors { 431 - let accent = Scale12::from_oklch_curve(ACCENT_HUE, &ACCENT_CHROMAS, lightness); 432 434 Colors { 433 435 neutral: Scale12::from_oklch_curve(NEUTRAL_HUE, &NEUTRAL_CHROMAS, lightness), 434 - accent, 436 + accent: Scale12::from_oklch_curve(ACCENT_HUE, &ACCENT_CHROMAS, lightness), 435 437 info: Scale12::from_oklch_curve(INFO_HUE, &SEMANTIC_CHROMAS, lightness), 436 438 success: Scale12::from_oklch_curve(SUCCESS_HUE, &SEMANTIC_CHROMAS, lightness), 437 439 warning: Scale12::from_oklch_curve(WARNING_HUE, &SEMANTIC_CHROMAS, lightness), 438 440 danger: Scale12::from_oklch_curve(DANGER_HUE, &SEMANTIC_CHROMAS, lightness), 439 - focus_ring: accent.step(Step12::SOLID), 440 441 } 441 442 } 442 443 ··· 458 459 } 459 460 460 461 #[test] 461 - fn on_surface_picks_dark_text_over_light_bg() { 462 + fn contrast_text_picks_dark_text_over_light_surface() { 462 463 let bg = Color::from_srgb_u8(0xFF, 0xFF, 0xFF); 463 - let text = bg.on_surface(); 464 + let text = light_colors().contrast_text(bg); 464 465 assert!(text.relative_luminance() < 0.1); 465 466 } 466 467 467 468 #[test] 468 - fn on_surface_picks_light_text_over_dark_bg() { 469 + fn contrast_text_picks_light_text_over_dark_surface() { 469 470 let bg = Color::from_srgb_u8(0x10, 0x10, 0x10); 470 - let text = bg.on_surface(); 471 + let text = dark_colors().contrast_text(bg); 471 472 assert!(text.relative_luminance() > 0.9); 472 473 } 473 474 ··· 539 540 } 540 541 541 542 #[test] 542 - fn focus_ring_is_in_gamut() { 543 - let ring = light_colors().focus_ring; 544 - let [r, g, b, a] = ring.linear_rgba_premul(); 543 + fn focus_ring_tracks_accent_solid() { 544 + let colors = light_colors(); 545 + assert_eq!(colors.focus_ring(), colors.accent_solid()); 546 + let [r, g, b, a] = colors.focus_ring().linear_rgba_premul(); 545 547 assert!((0.0..=1.0).contains(&r)); 546 548 assert!((0.0..=1.0).contains(&g)); 547 549 assert!((0.0..=1.0).contains(&b));
+5 -3
crates/bone-ui/src/widgets/button.rs
··· 99 99 let live_focused = ctx.is_focused(button.id); 100 100 let activated_via_pointer = interactive && interaction.click(); 101 101 let activated_via_key = interactive && live_focused && take_activation(ctx.input); 102 - let visuals = button_visuals(&ctx.theme, button.variant, button.state, interaction); 102 + let visuals = button_visuals(ctx.theme(), button.variant, button.state, interaction); 103 103 let paint = build_paint(ctx, &button, &visuals, live_focused); 104 104 ButtonResponse { 105 105 interaction, ··· 216 216 theme.colors.text_disabled() 217 217 } else { 218 218 match variant { 219 - ButtonVariant::Primary | ButtonVariant::Destructive => surface_fill.on_surface(), 219 + ButtonVariant::Primary | ButtonVariant::Destructive => { 220 + theme.colors.contrast_text(surface_fill) 221 + } 220 222 ButtonVariant::Secondary | ButtonVariant::Ghost | ButtonVariant::IconOnly => { 221 223 theme.colors.text_primary() 222 224 } ··· 250 252 paint.push(WidgetPaint::Mark { 251 253 rect: button.rect, 252 254 kind: super::paint::GlyphMark::Spinner, 253 - color: ctx.theme.colors.surface(SurfaceLevel::L1), 255 + color: ctx.theme().colors.surface(SurfaceLevel::L1), 254 256 }); 255 257 } 256 258 push_focus_ring(
+1 -1
crates/bone-ui/src/widgets/checkbox.rs
··· 120 120 mark, 121 121 active: state.is_active(), 122 122 disabled: checkbox.disabled, 123 - radius: ctx.theme.radius.sm, 123 + radius: ctx.theme().radius.sm, 124 124 }, 125 125 interaction, 126 126 live_focused,
+11 -11
crates/bone-ui/src/widgets/dialog.rs
··· 60 60 rect: modal.viewport, 61 61 fill: Color::TRANSPARENT.with_alpha(0.45), 62 62 border: None, 63 - radius: ctx.theme.radius.none, 63 + radius: ctx.theme().radius.none, 64 64 elevation: None, 65 65 }); 66 66 } 67 67 let body_rect = center_rect(modal.viewport, modal.size); 68 68 paint.push(WidgetPaint::Surface { 69 69 rect: body_rect, 70 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L1), 70 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L1), 71 71 border: Some(Border { 72 72 width: StrokeWidth::HAIRLINE, 73 - color: ctx.theme.colors.neutral.step(Step12::BORDER), 73 + color: ctx.theme().colors.neutral.step(Step12::BORDER), 74 74 }), 75 - radius: ctx.theme.radius.md, 76 - elevation: Some(ctx.theme.elevation.level1), 75 + radius: ctx.theme().radius.md, 76 + elevation: Some(ctx.theme().elevation.level1), 77 77 }); 78 78 let dismissed = take_key(ctx.input, &[TakeKey::named(NamedKey::Escape)]).is_some(); 79 79 let extras = body(ctx, body_rect, &mut paint); ··· 198 198 paint.push(WidgetPaint::Label { 199 199 rect: title_label_rect(title_rect), 200 200 text: LabelText::Key(title), 201 - color: ctx.theme.colors.text_primary(), 202 - role: ctx.theme.typography.heading, 201 + color: ctx.theme().colors.text_primary(), 202 + role: ctx.theme().typography.heading, 203 203 }); 204 204 paint.push(WidgetPaint::Surface { 205 205 rect: divider_rect(title_rect), 206 - fill: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 206 + fill: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 207 207 border: None, 208 - radius: ctx.theme.radius.none, 208 + radius: ctx.theme().radius.none, 209 209 elevation: None, 210 210 }); 211 211 let extras = body(ctx, body_rect, paint); ··· 405 405 paint.push(WidgetPaint::Label { 406 406 rect: body_rect, 407 407 text: LabelText::Key(message), 408 - color: ctx.theme.colors.text_primary(), 409 - role: ctx.theme.typography.body, 408 + color: ctx.theme().colors.text_primary(), 409 + role: ctx.theme().typography.body, 410 410 }); 411 411 }, 412 412 );
+16 -16
crates/bone-ui/src/widgets/dropdown.rs
··· 234 234 } 235 235 paint.push(WidgetPaint::Surface { 236 236 rect: popup_rect, 237 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L1), 237 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L1), 238 238 border: Some(Border { 239 239 width: StrokeWidth::HAIRLINE, 240 - color: ctx.theme.colors.neutral.step(Step12::BORDER), 240 + color: ctx.theme().colors.neutral.step(Step12::BORDER), 241 241 }), 242 - radius: ctx.theme.radius.sm, 243 - elevation: Some(ctx.theme.elevation.level1), 242 + radius: ctx.theme().radius.sm, 243 + elevation: Some(ctx.theme().elevation.level1), 244 244 }); 245 245 let prev_hovered = state.last_hovered; 246 246 let mut current_hovered: Option<usize> = None; ··· 268 268 paint.push(WidgetPaint::Surface { 269 269 rect: item_rect, 270 270 fill: if highlighted { 271 - ctx.theme.colors.neutral.step(Step12::HOVER_BG) 271 + ctx.theme().colors.neutral.step(Step12::HOVER_BG) 272 272 } else if Some(&item.value) == initial_selected { 273 - ctx.theme.colors.neutral.step(Step12::SELECTED_BG) 273 + ctx.theme().colors.neutral.step(Step12::SELECTED_BG) 274 274 } else { 275 - ctx.theme.colors.surface(crate::theme::SurfaceLevel::L1) 275 + ctx.theme().colors.surface(crate::theme::SurfaceLevel::L1) 276 276 }, 277 277 border: None, 278 - radius: ctx.theme.radius.none, 278 + radius: ctx.theme().radius.none, 279 279 elevation: None, 280 280 }); 281 281 paint.push(WidgetPaint::Label { 282 282 rect: item_rect, 283 283 text: LabelText::Key(item.label), 284 - color: ctx.theme.colors.text_primary(), 285 - role: ctx.theme.typography.body, 284 + color: ctx.theme().colors.text_primary(), 285 + role: ctx.theme().typography.body, 286 286 }); 287 287 }); 288 288 state.last_hovered = current_hovered; ··· 529 529 interaction: Interaction, 530 530 live_focused: bool, 531 531 ) -> Vec<WidgetPaint> { 532 - let neutral = ctx.theme.colors.neutral; 533 - let radius = ctx.theme.radius.sm; 532 + let neutral = ctx.theme().colors.neutral; 533 + let radius = ctx.theme().radius.sm; 534 534 let hovered = interaction.hover(); 535 535 let pressed = interaction.pressed(); 536 536 let fill = if disabled { ··· 562 562 rect: trigger_rect, 563 563 text: LabelText::Key(label_text), 564 564 color: if disabled { 565 - ctx.theme.colors.text_disabled() 565 + ctx.theme().colors.text_disabled() 566 566 } else { 567 - ctx.theme.colors.text_primary() 567 + ctx.theme().colors.text_primary() 568 568 }, 569 - role: ctx.theme.typography.body, 569 + role: ctx.theme().typography.body, 570 570 }, 571 571 WidgetPaint::Mark { 572 572 rect: trigger_rect, 573 573 kind: GlyphMark::Chevron, 574 - color: ctx.theme.colors.text_secondary(), 574 + color: ctx.theme().colors.text_secondary(), 575 575 }, 576 576 ]; 577 577 push_focus_ring(ctx, &mut paint, trigger_rect, radius, live_focused);
+5 -5
crates/bone-ui/src/widgets/file_picker.rs
··· 122 122 paint.push(WidgetPaint::Label { 123 123 rect: path_label_rect(body_rect), 124 124 text: LabelText::Key(current_path), 125 - color: ctx.theme.colors.text_secondary(), 126 - role: ctx.theme.typography.caption, 125 + color: ctx.theme().colors.text_secondary(), 126 + role: ctx.theme().typography.caption, 127 127 }); 128 128 let list_rect = list_rect_for(body_rect, mode); 129 129 paint.push(WidgetPaint::Surface { 130 130 rect: list_rect, 131 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L0), 131 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0), 132 132 border: Some(crate::theme::Border { 133 133 width: crate::theme::StrokeWidth::HAIRLINE, 134 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 134 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 135 135 }), 136 - radius: ctx.theme.radius.sm, 136 + radius: ctx.theme().radius.sm, 137 137 elevation: None, 138 138 }); 139 139 let list_response = show_list_view(
+5 -5
crates/bone-ui/src/widgets/hotkey_capture.rs
··· 147 147 interaction: Interaction, 148 148 live_focused: bool, 149 149 ) -> Vec<WidgetPaint> { 150 - let neutral = ctx.theme.colors.neutral; 151 - let radius = ctx.theme.radius.sm; 150 + let neutral = ctx.theme().colors.neutral; 151 + let radius = ctx.theme().radius.sm; 152 152 let hovered = interaction.hover(); 153 153 let fill = if disabled { 154 154 neutral.step(Step12::SUBTLE_BG) 155 155 } else if recording { 156 - ctx.theme.colors.accent.step(Step12::SELECTED_BG) 156 + ctx.theme().colors.accent.step(Step12::SELECTED_BG) 157 157 } else if hovered { 158 158 neutral.step(Step12::HOVER_BG) 159 159 } else { ··· 178 178 WidgetPaint::Label { 179 179 rect, 180 180 text: label, 181 - color: ctx.theme.colors.text_primary(), 182 - role: ctx.theme.typography.label, 181 + color: ctx.theme().colors.text_primary(), 182 + role: ctx.theme().typography.label, 183 183 }, 184 184 ]; 185 185 push_focus_ring(ctx, &mut paint, rect, radius, live_focused);
+25 -25
crates/bone-ui/src/widgets/menu.rs
··· 126 126 let rect = menu_rect(origin, items, metrics); 127 127 let mut paint = vec![WidgetPaint::Surface { 128 128 rect, 129 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L1), 129 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L1), 130 130 border: Some(Border { 131 131 width: StrokeWidth::HAIRLINE, 132 - color: ctx.theme.colors.neutral.step(Step12::BORDER), 132 + color: ctx.theme().colors.neutral.step(Step12::BORDER), 133 133 }), 134 - radius: ctx.theme.radius.sm, 135 - elevation: Some(ctx.theme.elevation.level1), 134 + radius: ctx.theme().radius.sm, 135 + elevation: Some(ctx.theme().elevation.level1), 136 136 }]; 137 137 let layouts = item_rects(rect, items, metrics); 138 138 let mut activated: Option<WidgetId> = None; ··· 297 297 rect: label_only_rect(args.rect, args.metrics), 298 298 text: LabelText::Key(*label), 299 299 color: if *disabled { 300 - ctx.theme.colors.text_disabled() 300 + ctx.theme().colors.text_disabled() 301 301 } else { 302 - ctx.theme.colors.text_primary() 302 + ctx.theme().colors.text_primary() 303 303 }, 304 - role: ctx.theme.typography.body, 304 + role: ctx.theme().typography.body, 305 305 }); 306 306 if let Some(sc) = shortcut { 307 307 paint.push(WidgetPaint::Label { 308 308 rect: shortcut_rect(args.rect, args.metrics), 309 309 text: LabelText::Key(*sc), 310 - color: ctx.theme.colors.text_secondary(), 311 - role: ctx.theme.typography.caption, 310 + color: ctx.theme().colors.text_secondary(), 311 + role: ctx.theme().typography.caption, 312 312 }); 313 313 } 314 314 ItemDrawResult { ··· 335 335 paint.push(WidgetPaint::Label { 336 336 rect: label_only_rect(args.rect, args.metrics), 337 337 text: LabelText::Key(*label), 338 - color: ctx.theme.colors.text_primary(), 339 - role: ctx.theme.typography.body, 338 + color: ctx.theme().colors.text_primary(), 339 + role: ctx.theme().typography.body, 340 340 }); 341 341 paint.push(WidgetPaint::Mark { 342 342 rect: arrow_rect(args.rect, args.metrics), 343 343 kind: GlyphMark::SubmenuArrow, 344 - color: ctx.theme.colors.text_secondary(), 344 + color: ctx.theme().colors.text_secondary(), 345 345 }); 346 346 ItemDrawResult { 347 347 paint, ··· 359 359 } 360 360 vec![WidgetPaint::Surface { 361 361 rect, 362 - fill: ctx.theme.colors.accent.step(Step12::HOVER_BG), 362 + fill: ctx.theme().colors.accent.step(Step12::HOVER_BG), 363 363 border: None, 364 - radius: ctx.theme.radius.sm, 364 + radius: ctx.theme().radius.sm, 365 365 elevation: None, 366 366 }] 367 367 } ··· 380 380 ); 381 381 vec![WidgetPaint::Surface { 382 382 rect: line_rect, 383 - fill: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 383 + fill: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 384 384 border: None, 385 - radius: ctx.theme.radius.none, 385 + radius: ctx.theme().radius.none, 386 386 elevation: None, 387 387 }] 388 388 } ··· 643 643 } = bar; 644 644 let mut paint = vec![WidgetPaint::Surface { 645 645 rect, 646 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L0), 646 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0), 647 647 border: Some(Border { 648 648 width: StrokeWidth::HAIRLINE, 649 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 649 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 650 650 }), 651 - radius: ctx.theme.radius.none, 651 + radius: ctx.theme().radius.none, 652 652 elevation: None, 653 653 }]; 654 654 let entry_layouts = entry_rects(rect, entries, item_width); ··· 704 704 WidgetPaint::Surface { 705 705 rect: entry_rect, 706 706 fill: if is_open { 707 - ctx.theme.colors.neutral.step(Step12::SELECTED_BG) 707 + ctx.theme().colors.neutral.step(Step12::SELECTED_BG) 708 708 } else if interaction.hover() { 709 - ctx.theme.colors.neutral.step(Step12::HOVER_BG) 709 + ctx.theme().colors.neutral.step(Step12::HOVER_BG) 710 710 } else { 711 711 Color::TRANSPARENT 712 712 }, 713 713 border: None, 714 - radius: ctx.theme.radius.sm, 714 + radius: ctx.theme().radius.sm, 715 715 elevation: None, 716 716 }, 717 717 WidgetPaint::Label { ··· 728 728 ), 729 729 ), 730 730 text: LabelText::Key(entry.label), 731 - color: ctx.theme.colors.text_primary(), 732 - role: ctx.theme.typography.label, 731 + color: ctx.theme().colors.text_primary(), 732 + role: ctx.theme().typography.label, 733 733 }, 734 734 ]; 735 735 push_focus_ring( 736 736 ctx, 737 737 &mut paint, 738 738 entry_rect, 739 - ctx.theme.radius.sm, 739 + ctx.theme().radius.sm, 740 740 live_focused, 741 741 ); 742 742 paint
+11 -11
crates/bone-ui/src/widgets/panel.rs
··· 114 114 } 115 115 116 116 fn panel_surface(ctx: &FrameCtx<'_>, rect: LayoutRect, variant: PanelVariant) -> Vec<WidgetPaint> { 117 - let neutral = ctx.theme.colors.neutral; 117 + let neutral = ctx.theme().colors.neutral; 118 118 let (fill, border) = match variant { 119 - PanelVariant::Plain => (ctx.theme.colors.surface(SurfaceLevel::L0), None), 119 + PanelVariant::Plain => (ctx.theme().colors.surface(SurfaceLevel::L0), None), 120 120 PanelVariant::Card => ( 121 - ctx.theme.colors.surface(SurfaceLevel::L1), 121 + ctx.theme().colors.surface(SurfaceLevel::L1), 122 122 Some(Border { 123 123 width: StrokeWidth::HAIRLINE, 124 124 color: neutral.step(Step12::SUBTLE_BORDER), ··· 129 129 rect, 130 130 fill, 131 131 border, 132 - radius: ctx.theme.radius.sm, 132 + radius: ctx.theme().radius.sm, 133 133 elevation: None, 134 134 }] 135 135 } ··· 147 147 ); 148 148 let mut paint = vec![WidgetPaint::Surface { 149 149 rect: title_rect, 150 - fill: ctx.theme.colors.neutral.step(Step12::SUBTLE_BG), 150 + fill: ctx.theme().colors.neutral.step(Step12::SUBTLE_BG), 151 151 border: Some(Border { 152 152 width: StrokeWidth::HAIRLINE, 153 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 153 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 154 154 }), 155 - radius: ctx.theme.radius.sm, 155 + radius: ctx.theme().radius.sm, 156 156 elevation: None, 157 157 }]; 158 158 let toggle_id = id.child(WidgetKey::new("titlebar")); ··· 185 185 } else { 186 186 GlyphMark::DisclosureOpen 187 187 }, 188 - color: ctx.theme.colors.text_secondary(), 188 + color: ctx.theme().colors.text_secondary(), 189 189 }); 190 190 push_focus_ring( 191 191 ctx, 192 192 &mut paint, 193 193 title_rect, 194 - ctx.theme.radius.sm, 194 + ctx.theme().radius.sm, 195 195 live_focused, 196 196 ); 197 197 } 198 198 paint.push(WidgetPaint::Label { 199 199 rect: label_rect(title_rect, bar.collapsible), 200 200 text: LabelText::Key(bar.label), 201 - color: ctx.theme.colors.text_primary(), 202 - role: ctx.theme.typography.title, 201 + color: ctx.theme().colors.text_primary(), 202 + role: ctx.theme().typography.title, 203 203 }); 204 204 paint 205 205 }
+6 -6
crates/bone-ui/src/widgets/property_grid.rs
··· 85 85 } = grid; 86 86 let mut paint = vec![WidgetPaint::Surface { 87 87 rect, 88 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L0), 88 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0), 89 89 border: None, 90 - radius: ctx.theme.radius.none, 90 + radius: ctx.theme().radius.none, 91 91 elevation: None, 92 92 }]; 93 93 let changed_rows = rows ··· 98 98 paint.push(WidgetPaint::Label { 99 99 rect: label_rect_at(row_rect, label_width, padding), 100 100 text: LabelText::Key(row.label), 101 - color: ctx.theme.colors.text_secondary(), 102 - role: ctx.theme.typography.label, 101 + color: ctx.theme().colors.text_secondary(), 102 + role: ctx.theme().typography.label, 103 103 }); 104 104 paint.push(WidgetPaint::Surface { 105 105 rect: divider_rect(row_rect), 106 - fill: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 106 + fill: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 107 107 border: None, 108 - radius: ctx.theme.radius.none, 108 + radius: ctx.theme().radius.none, 109 109 elevation: None, 110 110 }); 111 111 let cell = PropertyCell {
+1 -1
crates/bone-ui/src/widgets/radio_group.rs
··· 115 115 mark: active.then_some(GlyphMark::RadioDot), 116 116 active, 117 117 disabled, 118 - radius: ctx.theme.radius.pill, 118 + radius: ctx.theme().radius.pill, 119 119 }, 120 120 interaction, 121 121 live_focused,
+8 -8
crates/bone-ui/src/widgets/ribbon.rs
··· 120 120 let tab_views: Vec<Tab> = build_tab_strip(tabs, strip_rect); 121 121 let mut paint = vec![WidgetPaint::Surface { 122 122 rect, 123 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L1), 123 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L1), 124 124 border: Some(Border { 125 125 width: StrokeWidth::HAIRLINE, 126 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 126 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 127 127 }), 128 - radius: ctx.theme.radius.none, 128 + radius: ctx.theme().radius.none, 129 129 elevation: None, 130 130 }]; 131 131 let tabs_response = show_tabs( ··· 223 223 .for_each(|(group, group_rect)| { 224 224 paint.push(WidgetPaint::Surface { 225 225 rect: *group_rect, 226 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L1), 226 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L1), 227 227 border: Some(Border { 228 228 width: StrokeWidth::HAIRLINE, 229 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 229 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 230 230 }), 231 - radius: ctx.theme.radius.sm, 231 + radius: ctx.theme().radius.sm, 232 232 elevation: None, 233 233 }); 234 234 let toolbar_rect = inner_toolbar_rect(*group_rect, group_label_height, group_padding); ··· 253 253 paint.push(WidgetPaint::Label { 254 254 rect: group_label_rect(*group_rect, group_label_height), 255 255 text: LabelText::Key(group.label), 256 - color: ctx.theme.colors.text_secondary(), 257 - role: ctx.theme.typography.caption, 256 + color: ctx.theme().colors.text_secondary(), 257 + role: ctx.theme().typography.caption, 258 258 }); 259 259 }); 260 260 paint
+4 -4
crates/bone-ui/src/widgets/slider.rs
··· 266 266 interaction: Interaction, 267 267 live_focused: bool, 268 268 ) -> Vec<WidgetPaint> { 269 - let neutral = ctx.theme.colors.neutral; 270 - let accent = ctx.theme.colors.accent; 271 - let radius = ctx.theme.radius.pill; 269 + let neutral = ctx.theme().colors.neutral; 270 + let accent = ctx.theme().colors.accent; 271 + let radius = ctx.theme().radius.pill; 272 272 let track_height = LayoutPx::new(4.0); 273 273 let track_y = LayoutPx::new( 274 274 rect.origin.y.value() + rect.size.height.value() / 2.0 - track_height.value() / 2.0, ··· 337 337 WidgetPaint::Mark { 338 338 rect: thumb_rect, 339 339 kind: GlyphMark::SliderThumb, 340 - color: filled_fill.on_surface(), 340 + color: ctx.theme().colors.contrast_text(filled_fill), 341 341 }, 342 342 ]; 343 343 push_focus_ring(ctx, &mut paint, thumb_rect, radius, live_focused);
+9 -9
crates/bone-ui/src/widgets/status_bar.rs
··· 79 79 pub fn show_status_bar(ctx: &mut FrameCtx<'_>, bar: StatusBar<'_>) -> StatusBarResponse { 80 80 let mut paint = vec![WidgetPaint::Surface { 81 81 rect: bar.rect, 82 - fill: ctx.theme.colors.neutral.step(Step12::SUBTLE_BG), 82 + fill: ctx.theme().colors.neutral.step(Step12::SUBTLE_BG), 83 83 border: Some(Border { 84 84 width: StrokeWidth::HAIRLINE, 85 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 85 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 86 86 }), 87 - radius: ctx.theme.radius.none, 87 + radius: ctx.theme().radius.none, 88 88 elevation: None, 89 89 }]; 90 90 let layouts = lay_out_items(bar.rect, bar.items); ··· 118 118 if interaction.hover() || interaction.pressed() { 119 119 paint.push(WidgetPaint::Surface { 120 120 rect, 121 - fill: ctx.theme.colors.neutral.step(if interaction.pressed() { 121 + fill: ctx.theme().colors.neutral.step(if interaction.pressed() { 122 122 Step12::SELECTED_BG 123 123 } else { 124 124 Step12::HOVER_BG 125 125 }), 126 126 border: None, 127 - radius: ctx.theme.radius.none, 127 + radius: ctx.theme().radius.none, 128 128 elevation: None, 129 129 }); 130 130 } ··· 138 138 rect: dot_rect, 139 139 fill: badge, 140 140 border: None, 141 - radius: ctx.theme.radius.pill, 141 + radius: ctx.theme().radius.pill, 142 142 elevation: None, 143 143 }); 144 144 } 145 145 paint.push(WidgetPaint::Label { 146 146 rect: label_rect(rect, item.badge.is_some()), 147 147 text: LabelText::Key(item.label), 148 - color: ctx.theme.colors.text_secondary(), 149 - role: ctx.theme.typography.caption, 148 + color: ctx.theme().colors.text_secondary(), 149 + role: ctx.theme().typography.caption, 150 150 }); 151 - push_focus_ring(ctx, &mut paint, rect, ctx.theme.radius.none, live_focused); 151 + push_focus_ring(ctx, &mut paint, rect, ctx.theme().radius.none, live_focused); 152 152 paint 153 153 } 154 154
+21 -21
crates/bone-ui/src/widgets/table.rs
··· 104 104 } = view; 105 105 let mut paint = vec![WidgetPaint::Surface { 106 106 rect, 107 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L0), 107 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0), 108 108 border: None, 109 - radius: ctx.theme.radius.none, 109 + radius: ctx.theme().radius.none, 110 110 elevation: None, 111 111 }]; 112 112 let entry_id = state ··· 138 138 rect: row_rect, 139 139 fill, 140 140 border: None, 141 - radius: ctx.theme.radius.none, 141 + radius: ctx.theme().radius.none, 142 142 elevation: None, 143 143 }); 144 144 paint.push(WidgetPaint::Label { 145 145 rect: row_rect, 146 146 text: LabelText::Key(item.label), 147 - color: ctx.theme.colors.text_primary(), 148 - role: ctx.theme.typography.body, 147 + color: ctx.theme().colors.text_primary(), 148 + role: ctx.theme().typography.body, 149 149 }); 150 150 push_focus_ring( 151 151 ctx, 152 152 &mut paint, 153 153 row_rect, 154 - ctx.theme.radius.none, 154 + ctx.theme().radius.none, 155 155 live_focused, 156 156 ); 157 157 }); ··· 179 179 interaction: &crate::hit_test::Interaction, 180 180 selected: bool, 181 181 ) -> Color { 182 - let neutral = ctx.theme.colors.neutral; 182 + let neutral = ctx.theme().colors.neutral; 183 183 if selected { 184 - ctx.theme.colors.accent.step(Step12::HOVER_BG) 184 + ctx.theme().colors.accent.step(Step12::HOVER_BG) 185 185 } else if interaction.hover() { 186 186 neutral.step(Step12::HOVER_BG) 187 187 } else { ··· 356 356 let (column_widths, column_x) = compute_column_layout(columns, state, rect); 357 357 let mut paint = vec![WidgetPaint::Surface { 358 358 rect, 359 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L0), 359 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0), 360 360 border: None, 361 - radius: ctx.theme.radius.none, 361 + radius: ctx.theme().radius.none, 362 362 elevation: None, 363 363 }]; 364 364 let mut sort_changed: Option<TableSort> = None; ··· 532 532 rect: row_rect, 533 533 fill, 534 534 border: None, 535 - radius: ctx.theme.radius.none, 535 + radius: ctx.theme().radius.none, 536 536 elevation: None, 537 537 }]; 538 538 column_widths ··· 546 546 LayoutSize::new(*width, row_height), 547 547 ), 548 548 text: LabelText::Key(*text), 549 - color: ctx.theme.colors.text_primary(), 550 - role: ctx.theme.typography.body, 549 + color: ctx.theme().colors.text_primary(), 550 + role: ctx.theme().typography.body, 551 551 }); 552 552 }); 553 553 push_focus_ring( 554 554 ctx, 555 555 &mut paint, 556 556 row_rect, 557 - ctx.theme.radius.none, 557 + ctx.theme().radius.none, 558 558 live_focused, 559 559 ); 560 560 paint ··· 636 636 paint.push(WidgetPaint::Surface { 637 637 rect: header_rect, 638 638 fill: if header_interaction.hover() { 639 - ctx.theme.colors.neutral.step(Step12::HOVER_BG) 639 + ctx.theme().colors.neutral.step(Step12::HOVER_BG) 640 640 } else { 641 - ctx.theme.colors.neutral.step(Step12::SUBTLE_BG) 641 + ctx.theme().colors.neutral.step(Step12::SUBTLE_BG) 642 642 }, 643 643 border: Some(Border { 644 644 width: StrokeWidth::HAIRLINE, 645 - color: ctx.theme.colors.neutral.step(Step12::SUBTLE_BORDER), 645 + color: ctx.theme().colors.neutral.step(Step12::SUBTLE_BORDER), 646 646 }), 647 - radius: ctx.theme.radius.none, 647 + radius: ctx.theme().radius.none, 648 648 elevation: None, 649 649 }); 650 650 paint.push(WidgetPaint::Label { 651 651 rect: header_rect, 652 652 text: LabelText::Key(column.label), 653 - color: ctx.theme.colors.text_primary(), 654 - role: ctx.theme.typography.label, 653 + color: ctx.theme().colors.text_primary(), 654 + role: ctx.theme().typography.label, 655 655 }); 656 656 if let Some(sort) = state.sort 657 657 && sort.column == column.id ··· 662 662 SortDirection::Ascending => GlyphMark::SortAscending, 663 663 SortDirection::Descending => GlyphMark::SortDescending, 664 664 }, 665 - color: ctx.theme.colors.text_secondary(), 665 + color: ctx.theme().colors.text_secondary(), 666 666 }); 667 667 } 668 668 paint
+10 -10
crates/bone-ui/src/widgets/tabs.rs
··· 154 154 rect: label_rect(tab.rect, tab.closable), 155 155 text: LabelText::Key(tab.label), 156 156 color: tab_label_color(ctx, is_active, tab.disabled), 157 - role: ctx.theme.typography.label, 157 + role: ctx.theme().typography.label, 158 158 }); 159 159 let mut closed = None; 160 160 let close_id = tabs_id.child_indexed(WidgetKey::new("close"), tab_close_index(tab.id)); ··· 171 171 paint.push(WidgetPaint::Surface { 172 172 rect: close_rect, 173 173 fill: if close_interaction.hover() && interactive { 174 - ctx.theme.colors.neutral.step(Step12::HOVER_BG) 174 + ctx.theme().colors.neutral.step(Step12::HOVER_BG) 175 175 } else { 176 176 Color::TRANSPARENT 177 177 }, 178 178 border: None, 179 - radius: ctx.theme.radius.sm, 179 + radius: ctx.theme().radius.sm, 180 180 elevation: None, 181 181 }); 182 182 paint.push(WidgetPaint::Mark { ··· 185 185 color: tab_label_color(ctx, is_active, tab.disabled), 186 186 }); 187 187 } 188 - push_focus_ring(ctx, &mut paint, tab.rect, ctx.theme.radius.sm, live_focused); 188 + push_focus_ring(ctx, &mut paint, tab.rect, ctx.theme().radius.sm, live_focused); 189 189 let activated = (interactive && !is_active && interaction.click()).then_some(tab.id); 190 190 PerTab { 191 191 activated, ··· 229 229 active: bool, 230 230 interaction: Interaction, 231 231 ) -> Vec<WidgetPaint> { 232 - let neutral = ctx.theme.colors.neutral; 233 - let accent = ctx.theme.colors.accent; 232 + let neutral = ctx.theme().colors.neutral; 233 + let accent = ctx.theme().colors.accent; 234 234 let fill = if interaction.disabled() { 235 235 neutral.step(Step12::SUBTLE_BG) 236 236 } else if active { ··· 250 250 rect, 251 251 fill, 252 252 border, 253 - radius: ctx.theme.radius.sm, 253 + radius: ctx.theme().radius.sm, 254 254 elevation: None, 255 255 }] 256 256 } 257 257 258 258 fn tab_label_color(ctx: &FrameCtx<'_>, active: bool, disabled: bool) -> Color { 259 259 if disabled { 260 - ctx.theme.colors.text_disabled() 260 + ctx.theme().colors.text_disabled() 261 261 } else if active { 262 - ctx.theme.colors.text_primary() 262 + ctx.theme().colors.text_primary() 263 263 } else { 264 - ctx.theme.colors.text_secondary() 264 + ctx.theme().colors.text_secondary() 265 265 } 266 266 } 267 267
+10 -10
crates/bone-ui/src/widgets/text_input.rs
··· 433 433 interaction: Interaction, 434 434 has_error: bool, 435 435 ) -> FieldVisuals { 436 - let neutral = ctx.theme.colors.neutral; 437 - let danger = ctx.theme.colors.danger; 438 - let radius = ctx.theme.radius.sm; 436 + let neutral = ctx.theme().colors.neutral; 437 + let danger = ctx.theme().colors.danger; 438 + let radius = ctx.theme().radius.sm; 439 439 let hovered = interaction.hover(); 440 440 let focused = interaction.focused(); 441 441 let fill = if disabled { ··· 446 446 let border_color = if has_error { 447 447 danger.step(Step12::SOLID) 448 448 } else if focused { 449 - ctx.theme.colors.accent_solid() 449 + ctx.theme().colors.accent_solid() 450 450 } else if hovered { 451 451 neutral.step(Step12::HOVER_BORDER) 452 452 } else { ··· 462 462 elevation: None, 463 463 }; 464 464 let text_color = if disabled { 465 - ctx.theme.colors.text_disabled() 465 + ctx.theme().colors.text_disabled() 466 466 } else { 467 - ctx.theme.colors.text_primary() 467 + ctx.theme().colors.text_primary() 468 468 }; 469 469 FieldVisuals { 470 470 surface, 471 471 text: TextVisuals { 472 472 color: text_color, 473 - role: ctx.theme.typography.body, 473 + role: ctx.theme().typography.body, 474 474 }, 475 - placeholder: ctx.theme.colors.text_secondary(), 476 - caret: ctx.theme.colors.text_primary(), 477 - selection: ctx.theme.cad.selection_primary.with_alpha(0.35), 475 + placeholder: ctx.theme().colors.text_secondary(), 476 + caret: ctx.theme().colors.text_primary(), 477 + selection: ctx.theme().cad.selection_primary.with_alpha(0.35), 478 478 } 479 479 } 480 480
+19 -19
crates/bone-ui/src/widgets/toast.rs
··· 132 132 width: StrokeWidth::HAIRLINE, 133 133 color: border_color(ctx, kind), 134 134 }), 135 - radius: ctx.theme.radius.md, 136 - elevation: Some(ctx.theme.elevation.level1), 135 + radius: ctx.theme().radius.md, 136 + elevation: Some(ctx.theme().elevation.level1), 137 137 }); 138 138 paint.push(WidgetPaint::Mark { 139 139 rect: leading_mark_rect(rect), ··· 143 143 paint.push(WidgetPaint::Label { 144 144 rect: message_rect(rect, dismissible), 145 145 text: LabelText::Key(message), 146 - color: ctx.theme.colors.text_primary(), 147 - role: ctx.theme.typography.body, 146 + color: ctx.theme().colors.text_primary(), 147 + role: ctx.theme().typography.body, 148 148 }); 149 149 let mut dismissed_now = false; 150 150 if dismissible { ··· 162 162 paint.push(WidgetPaint::Surface { 163 163 rect: close_rect, 164 164 fill: if interaction.hover() { 165 - ctx.theme.colors.neutral.step(Step12::HOVER_BG) 165 + ctx.theme().colors.neutral.step(Step12::HOVER_BG) 166 166 } else { 167 167 Color::TRANSPARENT 168 168 }, 169 169 border: None, 170 - radius: ctx.theme.radius.sm, 170 + radius: ctx.theme().radius.sm, 171 171 elevation: None, 172 172 }); 173 173 paint.push(WidgetPaint::Mark { 174 174 rect: close_rect, 175 175 kind: GlyphMark::Close, 176 - color: ctx.theme.colors.text_secondary(), 176 + color: ctx.theme().colors.text_secondary(), 177 177 }); 178 178 } 179 179 ToastResponse { ··· 185 185 186 186 fn surface_fill(ctx: &FrameCtx<'_>, kind: ToastKind) -> Color { 187 187 let scale = match kind { 188 - ToastKind::Info => ctx.theme.colors.info, 189 - ToastKind::Success => ctx.theme.colors.success, 190 - ToastKind::Warning => ctx.theme.colors.warning, 191 - ToastKind::Danger => ctx.theme.colors.danger, 188 + ToastKind::Info => ctx.theme().colors.info, 189 + ToastKind::Success => ctx.theme().colors.success, 190 + ToastKind::Warning => ctx.theme().colors.warning, 191 + ToastKind::Danger => ctx.theme().colors.danger, 192 192 }; 193 193 scale.step(Step12::SUBTLE_BG) 194 194 } 195 195 196 196 fn border_color(ctx: &FrameCtx<'_>, kind: ToastKind) -> Color { 197 197 let scale = match kind { 198 - ToastKind::Info => ctx.theme.colors.info, 199 - ToastKind::Success => ctx.theme.colors.success, 200 - ToastKind::Warning => ctx.theme.colors.warning, 201 - ToastKind::Danger => ctx.theme.colors.danger, 198 + ToastKind::Info => ctx.theme().colors.info, 199 + ToastKind::Success => ctx.theme().colors.success, 200 + ToastKind::Warning => ctx.theme().colors.warning, 201 + ToastKind::Danger => ctx.theme().colors.danger, 202 202 }; 203 203 scale.step(Step12::BORDER) 204 204 } 205 205 206 206 fn leading_mark_color(ctx: &FrameCtx<'_>, kind: ToastKind) -> Color { 207 207 let scale = match kind { 208 - ToastKind::Info => ctx.theme.colors.info, 209 - ToastKind::Success => ctx.theme.colors.success, 210 - ToastKind::Warning => ctx.theme.colors.warning, 211 - ToastKind::Danger => ctx.theme.colors.danger, 208 + ToastKind::Info => ctx.theme().colors.info, 209 + ToastKind::Success => ctx.theme().colors.success, 210 + ToastKind::Warning => ctx.theme().colors.warning, 211 + ToastKind::Danger => ctx.theme().colors.danger, 212 212 }; 213 213 scale.step(Step12::SOLID) 214 214 }
+1 -1
crates/bone-ui/src/widgets/toggle_button.rs
··· 68 68 mark: None, 69 69 active: next_on, 70 70 disabled, 71 - radius: ctx.theme.radius.sm, 71 + radius: ctx.theme().radius.sm, 72 72 }, 73 73 interaction, 74 74 live_focused,
+13 -13
crates/bone-ui/src/widgets/toolbar.rs
··· 151 151 paint.push(WidgetPaint::Surface { 152 152 rect: overflow_rect, 153 153 fill: if *overflow_open { 154 - ctx.theme.colors.neutral.step(Step12::SELECTED_BG) 154 + ctx.theme().colors.neutral.step(Step12::SELECTED_BG) 155 155 } else if interaction.hover() { 156 - ctx.theme.colors.neutral.step(Step12::HOVER_BG) 156 + ctx.theme().colors.neutral.step(Step12::HOVER_BG) 157 157 } else { 158 - ctx.theme.colors.neutral.step(Step12::SUBTLE_BG) 158 + ctx.theme().colors.neutral.step(Step12::SUBTLE_BG) 159 159 }, 160 160 border: Some(Border { 161 161 width: StrokeWidth::HAIRLINE, 162 - color: ctx.theme.colors.neutral.step(Step12::BORDER), 162 + color: ctx.theme().colors.neutral.step(Step12::BORDER), 163 163 }), 164 - radius: ctx.theme.radius.sm, 164 + radius: ctx.theme().radius.sm, 165 165 elevation: None, 166 166 }); 167 167 paint.push(WidgetPaint::Mark { 168 168 rect: overflow_rect, 169 169 kind: GlyphMark::Ellipsis, 170 - color: ctx.theme.colors.text_secondary(), 170 + color: ctx.theme().colors.text_secondary(), 171 171 }); 172 172 push_focus_ring( 173 173 ctx, 174 174 &mut paint, 175 175 overflow_rect, 176 - ctx.theme.radius.sm, 176 + ctx.theme().radius.sm, 177 177 live_focused, 178 178 ); 179 179 } else { ··· 216 216 rect, 217 217 fill: item_fill(ctx, item, &interaction), 218 218 border: None, 219 - radius: ctx.theme.radius.sm, 219 + radius: ctx.theme().radius.sm, 220 220 elevation: None, 221 221 }]; 222 222 paint.push(WidgetPaint::Label { 223 223 rect, 224 224 text: LabelText::Key(item.label), 225 225 color: if item.disabled { 226 - ctx.theme.colors.text_disabled() 226 + ctx.theme().colors.text_disabled() 227 227 } else { 228 - ctx.theme.colors.text_primary() 228 + ctx.theme().colors.text_primary() 229 229 }, 230 - role: ctx.theme.typography.caption, 230 + role: ctx.theme().typography.caption, 231 231 }); 232 - push_focus_ring(ctx, &mut paint, rect, ctx.theme.radius.sm, live_focused); 232 + push_focus_ring(ctx, &mut paint, rect, ctx.theme().radius.sm, live_focused); 233 233 ItemDraw { 234 234 activated: activated_via_pointer || activated_via_key, 235 235 paint, ··· 241 241 item: &ToolbarItem, 242 242 interaction: &crate::hit_test::Interaction, 243 243 ) -> crate::theme::Color { 244 - let neutral = ctx.theme.colors.neutral; 244 + let neutral = ctx.theme().colors.neutral; 245 245 if item.disabled { 246 246 crate::theme::Color::TRANSPARENT 247 247 } else if item.active || interaction.pressed() {
+1 -1
crates/bone-ui/src/widgets/tooltip.rs
··· 86 86 rect, 87 87 text: LabelText::Key(tooltip.label), 88 88 anchor: tooltip.anchor, 89 - elevation: ctx.theme.elevation.level2, 89 + elevation: ctx.theme().elevation.level2, 90 90 }] 91 91 } 92 92
+11 -11
crates/bone-ui/src/widgets/tree_view.rs
··· 141 141 } = view; 142 142 let mut paint = vec![WidgetPaint::Surface { 143 143 rect, 144 - fill: ctx.theme.colors.surface(crate::theme::SurfaceLevel::L0), 144 + fill: ctx.theme().colors.surface(crate::theme::SurfaceLevel::L0), 145 145 border: None, 146 - radius: ctx.theme.radius.none, 146 + radius: ctx.theme().radius.none, 147 147 elevation: None, 148 148 }]; 149 149 let visible: Vec<VisibleRow> = flatten(roots, &state.expanded, 0); ··· 305 305 rect: row_rect, 306 306 fill: row_fill(ctx, &interaction, state.selection.contains(&row.id)), 307 307 border: None, 308 - radius: ctx.theme.radius.none, 308 + radius: ctx.theme().radius.none, 309 309 elevation: None, 310 310 }]; 311 311 if row.has_children { ··· 320 320 ctx, 321 321 &mut paint, 322 322 row_rect, 323 - ctx.theme.radius.none, 323 + ctx.theme().radius.none, 324 324 live_focused, 325 325 ); 326 326 if let Some(target) = state.drop_target ··· 328 328 { 329 329 paint.push(WidgetPaint::Surface { 330 330 rect: drop_indicator_rect(row_rect, target.placement), 331 - fill: ctx.theme.colors.accent.step(Step12::SOLID), 331 + fill: ctx.theme().colors.accent.step(Step12::SOLID), 332 332 border: None, 333 - radius: ctx.theme.radius.none, 333 + radius: ctx.theme().radius.none, 334 334 elevation: None, 335 335 }); 336 336 } ··· 414 414 } else { 415 415 GlyphMark::DisclosureClosed 416 416 }, 417 - color: ctx.theme.colors.text_secondary(), 417 + color: ctx.theme().colors.text_secondary(), 418 418 }] 419 419 } 420 420 ··· 422 422 WidgetPaint::Label { 423 423 rect: label_rect, 424 424 text: LabelText::Key(row.label), 425 - color: ctx.theme.colors.text_primary(), 426 - role: ctx.theme.typography.body, 425 + color: ctx.theme().colors.text_primary(), 426 + role: ctx.theme().typography.body, 427 427 } 428 428 } 429 429 ··· 487 487 interaction: &crate::hit_test::Interaction, 488 488 selected: bool, 489 489 ) -> Color { 490 - let neutral = ctx.theme.colors.neutral; 490 + let neutral = ctx.theme().colors.neutral; 491 491 if selected { 492 - ctx.theme.colors.accent.step(Step12::HOVER_BG) 492 + ctx.theme().colors.accent.step(Step12::HOVER_BG) 493 493 } else if interaction.hover() { 494 494 neutral.step(Step12::HOVER_BG) 495 495 } else {
+7 -7
crates/bone-ui/src/widgets/visuals.rs
··· 43 43 if live_focused && ctx.focus.focus_visible() { 44 44 paint.push(WidgetPaint::FocusRing { 45 45 rect, 46 - color: ctx.theme.colors.focus_ring, 46 + color: ctx.theme().colors.focus_ring(), 47 47 radius, 48 48 thickness: Spacing::px(StrokeWidth::HAIRLINE.value_px() * 2.0), 49 49 }); ··· 100 100 if disabled { 101 101 theme.colors.text_disabled() 102 102 } else if active { 103 - surface_fill.on_surface() 103 + theme.colors.contrast_text(surface_fill) 104 104 } else { 105 105 theme.colors.text_primary() 106 106 } ··· 131 131 disabled, 132 132 radius, 133 133 } = indicator; 134 - let fill = indicator_fill(&ctx.theme, active, disabled, interaction); 135 - let border = indicator_border(&ctx.theme, active, interaction.hover()); 134 + let fill = indicator_fill(ctx.theme(), active, disabled, interaction); 135 + let border = indicator_border(ctx.theme(), active, interaction.hover()); 136 136 paint.push(WidgetPaint::Surface { 137 137 rect, 138 138 fill, ··· 143 143 paint.push(WidgetPaint::Label { 144 144 rect, 145 145 text: LabelText::Key(label), 146 - color: indicator_label_color(&ctx.theme, fill, active, disabled), 147 - role: ctx.theme.typography.label, 146 + color: indicator_label_color(ctx.theme(), fill, active, disabled), 147 + role: ctx.theme().typography.label, 148 148 }); 149 149 if let Some(kind) = mark { 150 150 paint.push(WidgetPaint::Mark { 151 151 rect, 152 152 kind, 153 - color: fill.on_surface(), 153 + color: ctx.theme().colors.contrast_text(fill), 154 154 }); 155 155 } 156 156 push_focus_ring(ctx, paint, rect, radius, live_focused);
-6
crates/bone-ui/tests/snapshots/theme_snapshot__theme_dark.snap
··· 371 371 b: 0.792139, 372 372 a: 1.0, 373 373 )), 374 - focus_ring: Color( 375 - r: 0.0, 376 - g: 0.334456, 377 - b: 0.62022746, 378 - a: 1.0, 379 - ), 380 374 ), 381 375 cad: CadColors( 382 376 sketch_under_defined: Color(
-6
crates/bone-ui/tests/snapshots/theme_snapshot__theme_light.snap
··· 371 371 b: 0.0075402, 372 372 a: 1.0, 373 373 )), 374 - focus_ring: Color( 375 - r: 0.0, 376 - g: 0.334456, 377 - b: 0.62022746, 378 - a: 1.0, 379 - ), 380 374 ), 381 375 cad: CadColors( 382 376 sketch_under_defined: Color(