From 7c757ba4d4a94e692283856b9541eeef4c328f5d Mon Sep 17 00:00:00 2001 From: Jared Moulton Date: Wed, 13 Nov 2024 00:30:30 -0700 Subject: [PATCH] simplify widget gallery by using list (#683) As part of this, individual tab selectors are not marked as keyboard navigable but the list is. This makes it so that the items must be navigated to using the list selection when the list has focus. the previous behavior was strange as items could be navigated to separately using tab and the list selection. this led to confusion. --- examples/widget-gallery/src/main.rs | 145 ++++++++++------------------ src/theme.rs | 3 +- 2 files changed, 51 insertions(+), 97 deletions(-) diff --git a/examples/widget-gallery/src/main.rs b/examples/widget-gallery/src/main.rs index 811be911..001bc27c 100644 --- a/examples/widget-gallery/src/main.rs +++ b/examples/widget-gallery/src/main.rs @@ -16,24 +16,17 @@ pub mod rich_text; pub mod slider; use floem::{ - event::{Event, EventListener, EventPropagation}, + event::{Event, EventListener}, keyboard::{Key, NamedKey}, kurbo::Size, new_window, - peniko::Color, - reactive::{create_signal, SignalGet, SignalUpdate}, + prelude::*, style::{Background, CursorStyle, Transition}, - unit::{DurationUnitExt, UnitExt}, - views::{ - button, h_stack, label, scroll, stack, tab, v_stack, virtual_stack, Decorators, LabelClass, - VirtualDirection, VirtualItemSize, - }, window::WindowConfig, - IntoView, View, }; fn app_view() -> impl IntoView { - let tabs: im::Vector<&str> = vec![ + let tabs: Vec<&'static str> = vec![ "Label", "Button", "Checkbox", @@ -50,9 +43,7 @@ fn app_view() -> impl IntoView { "Draggable", "DroppedFile", "Files", - ] - .into_iter() - .collect(); + ]; let create_view = |it: &str| match it { "Label" => labels::label_view().into_any(), @@ -74,114 +65,74 @@ fn app_view() -> impl IntoView { _ => label(|| "Not implemented".to_owned()).into_any(), }; - let (tabs, _set_tabs) = create_signal(tabs); + let tabs = RwSignal::new(tabs); let (active_tab, set_active_tab) = create_signal(0); - let list = scroll({ - virtual_stack( - VirtualDirection::Vertical, - VirtualItemSize::Fixed(Box::new(|| 36.0)), - move || tabs.get(), - move |item| *item, - move |item| { - let index = tabs - .get_untracked() - .iter() - .position(|it| *it == item) - .unwrap(); - stack((label(move || item).style(|s| s.font_size(18.0)),)) - .on_click_stop(move |_| { - set_active_tab.update(|v: &mut usize| { - *v = tabs - .get_untracked() - .iter() - .position(|it| *it == item) - .unwrap(); - }); + let side_tab_bar = list(tabs.get().into_iter().enumerate().map(move |(idx, item)| { + label(move || item) + .draggable() + .style(move |s| { + s.flex_row() + .font_size(18.) + .padding(5.0) + .width(100.pct()) + .height(36.0) + .transition(Background, Transition::ease_in_out(100.millis())) + .items_center() + .border_bottom(1.) + .border_color(Color::LIGHT_GRAY) + .selected(|s| { + s.border(2.) + .border_color(Color::BLUE) + .background(Color::GRAY.multiply_alpha(0.6)) }) - .on_event(EventListener::KeyDown, move |e| { - if let Event::KeyDown(key_event) = e { - let active = active_tab.get(); - if key_event.modifiers.is_empty() { - match key_event.key.logical_key { - Key::Named(NamedKey::ArrowUp) => { - if active > 0 { - set_active_tab.update(|v| *v -= 1) - } - EventPropagation::Stop - } - Key::Named(NamedKey::ArrowDown) => { - if active < tabs.get().len() - 1 { - set_active_tab.update(|v| *v += 1) - } - EventPropagation::Stop - } - _ => EventPropagation::Continue, - } - } else { - EventPropagation::Continue - } - } else { - EventPropagation::Continue - } + .hover(|s| { + s.background(Color::LIGHT_GRAY) + .apply_if(idx == active_tab.get(), |s| s.background(Color::GRAY)) + .cursor(CursorStyle::Pointer) }) - .keyboard_navigable() - .draggable() - .style(move |s| { - s.flex_row() - .padding(5.0) - .width(100.pct()) - .height(36.0) - .transition(Background, Transition::ease_in_out(400.millis())) - .items_center() - .border_bottom(1.0) - .border_color(Color::LIGHT_GRAY) - .apply_if(index == active_tab.get(), |s| { - s.background(Color::GRAY.multiply_alpha(0.6)) - }) - .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) - .hover(|s| { - s.background(Color::LIGHT_GRAY) - .apply_if(index == active_tab.get(), |s| { - s.background(Color::GRAY) - }) - .cursor(CursorStyle::Pointer) - }) - }) - }, - ) - .style(|s| s.flex_col().width(140.0)) + }) + .dragging_style(|s| s.background(Color::GRAY.multiply_alpha(0.6))) + })) + .on_select(move |idx| { + if let Some(idx) = idx { + set_active_tab.set(idx); + } }) + .keyboard_navigable() + .style(|s| s.flex_col().width(140.0)) + .scroll() + .debug_name("Side Tab Bar") .scroll_style(|s| s.shrink_to_fit()) .style(|s| { s.border(1.) + .padding(3.) .border_color(Color::GRAY) .class(LabelClass, |s| s.selectable(false)) }); - let id = list.id(); + let id = side_tab_bar.id(); let inspector = button("Open Inspector") .action(move || id.inspect()) .style(|s| s); let new_window = button("Open In Window").action(move || { - let mut name = ""; - let active = active_tab.get(); - if active < tabs.get().len() { - name = tabs.get().get(active_tab.get()).unwrap_or(&name); - } + let name = tabs.with(|tabs| tabs.get(active_tab.get()).copied()); new_window( - move |_| create_view(name), + move |_| create_view(name.unwrap_or_default()), Some( WindowConfig::default() .size(Size::new(700.0, 400.0)) - .title(name), + .title(name.unwrap_or_default()), ), ); }); - let left = v_stack((list, new_window, inspector)).style(|s| s.height_full().column_gap(5.0)); + let left_side_bar = (side_tab_bar, new_window, inspector) + .v_stack() + .debug_name("Left Side Bar") + .style(|s| s.height_full().column_gap(5.0)); let tab = tab( move || active_tab.get(), @@ -189,11 +140,13 @@ fn app_view() -> impl IntoView { |it| *it, create_view, ) + .debug_name("Active Tab") .style(|s| s.flex_col().items_start()); let tab = scroll(tab).scroll_style(|s| s.shrink_to_fit()); - let view = h_stack((left, tab)) + let view = (left_side_bar, tab) + .h_stack() .style(|s| s.padding(5.0).width_full().height_full().row_gap(5.0)) .window_title(|| "Widget Gallery".to_owned()); diff --git a/src/theme.rs b/src/theme.rs index 7ac520ae..3ccc4922 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -214,7 +214,8 @@ pub(crate) fn default_theme() -> Theme { let theme = Style::new() .class(ListClass, |s| { - s.focus(|s| s.class(ListItemClass, |_| item_focused_style)) + s.apply(focus_style) + .focus(|s| s.class(ListItemClass, |_| item_focused_style)) .class(ListItemClass, |_| item_unfocused_style) }) .class(LabeledCheckboxClass, |_| labeled_checkbox_style)