Wait https://github.com/zed-industries/zed/pull/33008, https://github.com/zed-industries/zed/pull/34804 to merge. https://github.com/user-attachments/assets/3c21ebc9-44a0-4311-bb0f-5f63fe8d4bb3 - Improved `focus_ring` to render like an outline. - Added to support press `Enter`, `Space` key to trigger Checkbox, Button, Radio. ## Break Changes - The `FocusableExt` has been changed to only for crate internal. - The `FocusableCycle` has been removed, now GPUI has it own tab stop implementation.
320 lines
11 KiB
Rust
320 lines
11 KiB
Rust
use gpui::{
|
|
hsla, px, App, AppContext, Context, Entity, Focusable, Hsla, IntoElement, ParentElement,
|
|
Render, SharedString, Styled, Subscription, Window,
|
|
};
|
|
use gpui_component::{
|
|
checkbox::Checkbox,
|
|
clipboard::Clipboard,
|
|
h_flex,
|
|
slider::{Slider, SliderEvent, SliderState},
|
|
v_flex, ActiveTheme, Colorize as _, ContextModal, StyledExt,
|
|
};
|
|
|
|
use crate::section;
|
|
|
|
pub struct SliderStory {
|
|
focus_handle: gpui::FocusHandle,
|
|
slider1: Entity<SliderState>,
|
|
slider1_value: f32,
|
|
slider2: Entity<SliderState>,
|
|
slider2_value: f32,
|
|
slider3: Entity<SliderState>,
|
|
slider_hsl: [Entity<SliderState>; 4],
|
|
slider_hsl_value: Hsla,
|
|
slider4: Entity<SliderState>,
|
|
disabled: bool,
|
|
_subscritions: Vec<Subscription>,
|
|
}
|
|
|
|
impl super::Story for SliderStory {
|
|
fn title() -> &'static str {
|
|
"Slider"
|
|
}
|
|
|
|
fn description() -> &'static str {
|
|
"Displays a slider control for selecting a value within a range."
|
|
}
|
|
|
|
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {
|
|
Self::view(window, cx)
|
|
}
|
|
}
|
|
|
|
impl SliderStory {
|
|
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
|
cx.new(|cx| Self::new(window, cx))
|
|
}
|
|
|
|
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
|
|
let slider1 = cx.new(|_| {
|
|
SliderState::new()
|
|
.min(-255.)
|
|
.max(255.)
|
|
.default_value(75.)
|
|
.step(15.)
|
|
});
|
|
|
|
let slider2 = cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(5.)
|
|
.step(1.0)
|
|
.default_value(2.)
|
|
});
|
|
let slider_hsl = [
|
|
cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(1.)
|
|
.step(0.01)
|
|
.default_value(0.38)
|
|
}),
|
|
cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(1.)
|
|
.step(0.01)
|
|
.default_value(0.5)
|
|
}),
|
|
cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(1.)
|
|
.step(0.01)
|
|
.default_value(0.5)
|
|
}),
|
|
cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(1.)
|
|
.step(0.01)
|
|
.default_value(0.5)
|
|
}),
|
|
];
|
|
|
|
let slider3 = cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(100.)
|
|
.default_value(12.0..45.0)
|
|
.step(1.)
|
|
});
|
|
|
|
let slider4 = cx.new(|_| {
|
|
SliderState::new()
|
|
.min(0.)
|
|
.max(360.)
|
|
.default_value(100.0..300.0)
|
|
.step(1.)
|
|
});
|
|
|
|
let mut _subscritions = vec![
|
|
cx.subscribe(&slider1, |this, _, event: &SliderEvent, cx| match event {
|
|
SliderEvent::Change(value) => {
|
|
this.slider1_value = value.start();
|
|
cx.notify();
|
|
}
|
|
}),
|
|
cx.subscribe(&slider2, |this, _, event: &SliderEvent, cx| match event {
|
|
SliderEvent::Change(value) => {
|
|
this.slider2_value = value.start();
|
|
cx.notify();
|
|
}
|
|
}),
|
|
];
|
|
|
|
_subscritions.extend(
|
|
slider_hsl
|
|
.iter()
|
|
.map(|slider| {
|
|
cx.subscribe(slider, |this, _, event: &SliderEvent, cx| match event {
|
|
SliderEvent::Change(_) => {
|
|
this.slider_hsl_value = hsla(
|
|
this.slider_hsl[0].read(cx).value().start(),
|
|
this.slider_hsl[1].read(cx).value().start(),
|
|
this.slider_hsl[2].read(cx).value().start(),
|
|
this.slider_hsl[3].read(cx).value().start(),
|
|
);
|
|
cx.notify();
|
|
}
|
|
})
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
);
|
|
|
|
slider_hsl[0].update(cx, |slider, cx| {
|
|
cx.emit(SliderEvent::Change(slider.value()));
|
|
});
|
|
|
|
Self {
|
|
focus_handle: cx.focus_handle(),
|
|
slider1_value: 0.,
|
|
slider2_value: 0.,
|
|
slider1,
|
|
slider2,
|
|
slider3,
|
|
slider4,
|
|
slider_hsl,
|
|
slider_hsl_value: gpui::red(),
|
|
disabled: false,
|
|
_subscritions,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Focusable for SliderStory {
|
|
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
|
|
self.focus_handle.clone()
|
|
}
|
|
}
|
|
|
|
impl Render for SliderStory {
|
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let rgb = SharedString::from(self.slider_hsl_value.to_hex());
|
|
|
|
v_flex()
|
|
.gap_y_3()
|
|
.child(
|
|
h_flex().child(
|
|
Checkbox::new("disabled")
|
|
.checked(self.disabled)
|
|
.label("Disabled")
|
|
.on_click(cx.listener(|this, check: &bool, _, cx| {
|
|
this.disabled = *check;
|
|
cx.notify();
|
|
})),
|
|
),
|
|
)
|
|
.child(
|
|
section("Horizontal Slider")
|
|
.max_w_md()
|
|
.v_flex()
|
|
.child(Slider::new(&self.slider1).disabled(self.disabled))
|
|
.child(format!("Value: {}", self.slider1_value)),
|
|
)
|
|
.child(
|
|
section("Slider (0 - 5) and with color")
|
|
.max_w_md()
|
|
.v_flex()
|
|
.child(
|
|
Slider::new(&self.slider2)
|
|
.disabled(self.disabled)
|
|
.bg(cx.theme().success)
|
|
.text_color(cx.theme().success_foreground),
|
|
)
|
|
.child(format!("Value: {}", self.slider2_value)),
|
|
)
|
|
.child(
|
|
section("Range Mode")
|
|
.max_w_md()
|
|
.v_flex()
|
|
.child(Slider::new(&self.slider3).disabled(self.disabled))
|
|
.child(format!("Value: {}", self.slider3.read(cx).value())),
|
|
)
|
|
.child(
|
|
section("Vertical with Range")
|
|
.max_w_md()
|
|
.v_flex()
|
|
.child(
|
|
Slider::new(&self.slider4)
|
|
.vertical()
|
|
.h(px(200.))
|
|
.rounded(px(2.))
|
|
.disabled(self.disabled),
|
|
)
|
|
.child(format!("Value: {}", self.slider4.read(cx).value())),
|
|
)
|
|
.child(
|
|
section("Color Picker")
|
|
.sub_title(
|
|
h_flex()
|
|
.gap_2()
|
|
.items_center()
|
|
.child(
|
|
h_flex()
|
|
.text_color(self.slider_hsl_value)
|
|
.child(rgb.clone()),
|
|
)
|
|
.child(Clipboard::new("copy-hsl").value(rgb).on_copied(
|
|
|_, window, cx| {
|
|
window.push_notification("Color copied to clipboard.", cx)
|
|
},
|
|
)),
|
|
)
|
|
.max_w_md()
|
|
.justify_around()
|
|
.child(
|
|
v_flex()
|
|
.h_32()
|
|
.gap_3()
|
|
.items_center()
|
|
.justify_center()
|
|
.child(
|
|
Slider::new(&self.slider_hsl[0])
|
|
.vertical()
|
|
.disabled(self.disabled),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.items_center()
|
|
.child("Hue")
|
|
.child(format!("{:.0}", self.slider_hsl_value.h * 360.)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.h_32()
|
|
.gap_3()
|
|
.items_center()
|
|
.justify_center()
|
|
.child(
|
|
Slider::new(&self.slider_hsl[1])
|
|
.vertical()
|
|
.disabled(self.disabled),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.items_center()
|
|
.child("Saturation")
|
|
.child(format!("{:.0}", self.slider_hsl_value.s * 100.)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.h_32()
|
|
.gap_3()
|
|
.items_center()
|
|
.justify_center()
|
|
.child(
|
|
Slider::new(&self.slider_hsl[2])
|
|
.vertical()
|
|
.disabled(self.disabled),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.items_center()
|
|
.child("Lightness")
|
|
.child(format!("{:.0}", self.slider_hsl_value.l * 100.)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.h_32()
|
|
.gap_3()
|
|
.items_center()
|
|
.justify_center()
|
|
.child(
|
|
Slider::new(&self.slider_hsl[3])
|
|
.vertical()
|
|
.disabled(self.disabled),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.items_center()
|
|
.child("Alpha")
|
|
.child(format!("{:.0}", self.slider_hsl_value.a * 100.)),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
}
|