kitchen_ui/widgets/
accent_picker.rs1use gpui_kit::component::button::Button;
4use gpui_kit::component::color_picker::{ColorPicker, ColorPickerEvent, ColorPickerState};
5use gpui_kit::component::tooltip::Tooltip;
6use gpui_kit::component::{Selectable as _, Sizable as _, h_flex};
7use gpui_kit::prelude::FluentBuilder as _;
8use gpui_kit::{
9 AppContext as _, Context, Entity, EventEmitter, InteractiveElement as _, IntoElement, ParentElement as _, Render, SharedString,
10 StatefulInteractiveElement as _, Styled as _, Subscription, TestSupportExt as _, Window, div, px,
11};
12
13use crate::theme::{self, Rgb, color};
14use crate::widgets::swatch;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct AccentChanged(pub Option<Rgb>);
19
20pub struct AccentPicker {
26 current: Option<Rgb>,
27 suggested: Vec<Rgb>,
28 custom: Entity<ColorPickerState>,
29 _subs: Vec<Subscription>,
30}
31
32impl EventEmitter<AccentChanged> for AccentPicker {}
33
34impl AccentPicker {
35 pub fn new(current: Option<Rgb>, window: &mut Window, cx: &mut Context<Self>) -> Self {
37 let custom = cx.new(|cx| {
38 let state = ColorPickerState::new(window, cx);
39 match current {
40 Some(rgb) => state.default_value(rgb.hsla()),
41 None => state,
42 }
43 });
44 let sub = cx.subscribe(&custom, |this, _, e: &ColorPickerEvent, cx| {
45 if let ColorPickerEvent::Change(Some(c)) = e {
46 this.pick(Some(Rgb::from(*c)), cx);
47 }
48 });
49 Self { current, suggested: Rgb::SUGGESTED.to_vec(), custom, _subs: vec![sub] }
50 }
51
52 pub fn set_suggested(&mut self, colours: Vec<Rgb>, cx: &mut Context<Self>) {
54 self.suggested = colours;
55 cx.notify();
56 }
57
58 pub fn set_current(&mut self, current: Option<Rgb>, cx: &mut Context<Self>) {
60 self.current = current;
61 cx.notify();
62 }
63
64 pub fn current(&self) -> Option<Rgb> {
66 self.current
67 }
68
69 pub fn pick(&mut self, accent: Option<Rgb>, cx: &mut Context<Self>) {
71 if self.current != accent {
72 self.current = accent;
73 cx.emit(AccentChanged(accent));
74 cx.notify();
75 }
76 }
77}
78
79impl Render for AccentPicker {
80 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
81 let current = self.current;
82 let app = Button::new("accent-app").small().outline().label("App").selected(current.is_none()).tooltip("The app's own accent").on_click(cx.listener(|this, _, _, cx| this.pick(None, cx)));
83 let swatches = self.suggested.iter().copied().map(|rgb| {
84 let chosen = current == Some(rgb);
85 let tip = SharedString::from(rgb.to_string());
86 div()
87 .id(SharedString::from(format!("accent-{:06x}", rgb.0)))
88 .flex_none()
89 .size(px(theme::TARGET_MIN))
90 .flex()
91 .items_center()
92 .justify_center()
93 .rounded(px(theme::RADIUS))
94 .border_2()
95 .border_color(if chosen { color::text() } else { gpui_kit::transparent_black() })
96 .when(!chosen, |d| d.hover(|d| d.bg(color::hover())))
97 .cursor_pointer()
98 .child(swatch(rgb.hsla(), 16.))
99 .tooltip(move |window, cx| Tooltip::new(tip.clone()).build(window, cx))
100 .on_click(cx.listener(move |this, _, _, cx| this.pick(Some(rgb), cx)))
101 .test_support()
102 });
103 let custom_chosen = current.is_some_and(|c| !self.suggested.contains(&c));
104 let featured = self.suggested.iter().map(|c| c.hsla()).collect();
105 let custom = div()
106 .flex_none()
107 .rounded(px(theme::RADIUS))
108 .border_2()
109 .border_color(if custom_chosen { color::text() } else { gpui_kit::transparent_black() })
110 .child(ColorPicker::new(&self.custom).small().featured_colors(featured).label("Custom"));
111 h_flex().gap(px(theme::XS)).items_center().flex_wrap().child(app).children(swatches).child(custom)
112 }
113}