Add Scrollbar (#9)
https://github.com/huacnlee/gpui-component/assets/5518/5e4b7809-f2d0-4e45-a0f2-b9578323a11e
This commit is contained in:
parent
29dc803014
commit
d183bacc73
9 changed files with 356 additions and 28 deletions
|
|
@ -60,6 +60,7 @@ There have a part of UI components from [Zed](https://github.com/zed-industries/
|
|||
|
||||
- title_bar
|
||||
- picker
|
||||
- scrollbar
|
||||
|
||||
> I think we can discuss them with Zed team to change the license to MIT or Apache License for share to community.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use core::time;
|
||||
use std::{cell::RefCell, fmt::format, rc::Rc};
|
||||
|
||||
use fake::Fake;
|
||||
use gpui::{
|
||||
actions, div, impl_actions, prelude::FluentBuilder as _, px, Action, ElementId,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, Styled, Timer, View,
|
||||
ViewContext, VisualContext, WindowContext,
|
||||
actions, div, prelude::FluentBuilder as _, px, ElementId, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, RenderOnce, Styled, Timer, View, ViewContext, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
use ui::{
|
||||
|
|
@ -86,7 +85,6 @@ impl RenderOnce for CompanyListItem {
|
|||
self.base
|
||||
.px_3()
|
||||
.py_1()
|
||||
.rounded_lg()
|
||||
.overflow_x_hidden()
|
||||
.bg(bg_color)
|
||||
.child(
|
||||
|
|
@ -210,6 +208,7 @@ impl ListStory {
|
|||
},
|
||||
cx,
|
||||
)
|
||||
// .max_height(Some(px(350.0).into()))
|
||||
.no_query()
|
||||
});
|
||||
|
||||
|
|
@ -218,7 +217,7 @@ impl ListStory {
|
|||
loop {
|
||||
Timer::after(time::Duration::from_secs_f64(0.5)).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.company_list.update(cx, |picker, cx| {
|
||||
this.company_list.update(cx, |picker, _| {
|
||||
picker
|
||||
.delegate_mut()
|
||||
.companies
|
||||
|
|
@ -257,13 +256,13 @@ impl Render for ListStory {
|
|||
.gap_4()
|
||||
.mb_4()
|
||||
.child(
|
||||
div()
|
||||
v_flex()
|
||||
.h_full()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.p_1()
|
||||
.rounded_md()
|
||||
.w(px(400.))
|
||||
.occlude()
|
||||
.child(self.company_list.clone()),
|
||||
)
|
||||
.child(
|
||||
|
|
@ -280,7 +279,15 @@ impl Render for ListStory {
|
|||
div()
|
||||
.flex_1()
|
||||
.gap_2()
|
||||
.child(div().text_3xl().mb_6().child(company.name.clone()))
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.justify_between()
|
||||
.child(
|
||||
div().text_3xl().mb_6().child(company.name.clone()),
|
||||
)
|
||||
.child(format!("{:.2}", company.last_done)),
|
||||
)
|
||||
.child(company.industry.clone())
|
||||
.child(company.description.clone()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -172,8 +172,8 @@ impl PickerStory {
|
|||
},
|
||||
cx,
|
||||
)
|
||||
.modal(true)
|
||||
.max_height(Some(px(350.0).into()));
|
||||
.modal(true);
|
||||
|
||||
picker.focus(cx);
|
||||
picker.set_query("c", cx);
|
||||
picker
|
||||
|
|
@ -213,12 +213,13 @@ impl Render for PickerStory {
|
|||
.when(self.open, |this| {
|
||||
this.child(
|
||||
div().absolute().size_full().top_0().left_0().child(
|
||||
v_flex()
|
||||
.top_10()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.child(h_flex().w(px(450.)).occlude().child(self.picker.clone())),
|
||||
v_flex().top_10().flex().flex_col().items_center().child(
|
||||
div()
|
||||
.w(px(450.))
|
||||
.h(px(350.))
|
||||
.occlude()
|
||||
.child(self.picker.clone()),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -266,9 +266,9 @@ impl ButtonStyle {
|
|||
|
||||
fn hovered(&self, cx: &WindowContext) -> ButtonStyles {
|
||||
// Hover color = color/90
|
||||
let bg = self.bg_color(cx).divide(0.9);
|
||||
let border = self.border_color(cx).divide(0.9);
|
||||
let fg = self.text_color(cx).divide(0.9);
|
||||
let bg = self.bg_color(cx);
|
||||
let border = self.border_color(cx);
|
||||
let fg = self.text_color(cx);
|
||||
|
||||
ButtonStyles { bg, border, fg }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ where
|
|||
let picker = cx.new_view(|cx| {
|
||||
Picker::uniform_list(picker_delegate, cx)
|
||||
.no_query()
|
||||
.scrollbar_enable(false)
|
||||
.max_height(Some(rems(20.).into()))
|
||||
});
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod event;
|
|||
mod icon;
|
||||
mod platform;
|
||||
mod prelude;
|
||||
mod scrollbar;
|
||||
mod selectable;
|
||||
mod stock;
|
||||
mod styled_ext;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use std::{cell::Cell, rc::Rc, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
actions, div, list, prelude::FluentBuilder as _, px, rems, uniform_list, AppContext,
|
||||
ClickEvent, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||
IntoElement, KeyBinding, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent,
|
||||
actions, div, list, prelude::FluentBuilder as _, px, uniform_list, AppContext, ClickEvent,
|
||||
DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement,
|
||||
KeyBinding, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent,
|
||||
ParentElement as _, Render, SharedString, StatefulInteractiveElement as _, Styled as _, Task,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext as _, WindowContext,
|
||||
};
|
||||
|
|
@ -34,6 +36,7 @@ use crate::{
|
|||
divider::Divider,
|
||||
empty::Empty,
|
||||
input::{TextEvent, TextInput},
|
||||
scrollbar::Scrollbar,
|
||||
stock::*,
|
||||
theme::ActiveTheme,
|
||||
StyledExt as _,
|
||||
|
|
@ -149,6 +152,10 @@ pub struct Picker<D: PickerDelegate> {
|
|||
/// Just a empty view for holding the focus
|
||||
head: View<Empty>,
|
||||
pending_update_matches: Option<PendingUpdateMatches>,
|
||||
scrollbar_enable: bool,
|
||||
show_scrollbar: bool,
|
||||
hide_scrollbar_task: Option<Task<()>>,
|
||||
scrollbar_drag_state: Rc<Cell<Option<f32>>>,
|
||||
}
|
||||
|
||||
impl<D: PickerDelegate> Picker<D> {
|
||||
|
|
@ -190,6 +197,10 @@ impl<D: PickerDelegate> Picker<D> {
|
|||
max_height: None,
|
||||
element_container,
|
||||
pending_update_matches: None,
|
||||
scrollbar_enable: true,
|
||||
show_scrollbar: false,
|
||||
hide_scrollbar_task: None,
|
||||
scrollbar_drag_state: Rc::new(Cell::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,6 +260,11 @@ impl<D: PickerDelegate> Picker<D> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn scrollbar_enable(mut self, enable: bool) -> Self {
|
||||
self.scrollbar_enable = enable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_query(&mut self, query: &str, cx: &mut ViewContext<Self>) {
|
||||
if let Some(input) = &self.query_input {
|
||||
input.update(cx, |this, cx| this.set_text(query.to_string(), cx));
|
||||
|
|
@ -327,6 +343,46 @@ impl<D: PickerDelegate> Picker<D> {
|
|||
)
|
||||
}
|
||||
|
||||
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<Scrollbar> {
|
||||
if !self.scrollbar_enable {
|
||||
return None;
|
||||
}
|
||||
if !self.show_scrollbar {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(scroll_handle) = match &self.element_container {
|
||||
ElementContainer::List(_state) => None,
|
||||
ElementContainer::UniformList(scroll_handle) => Some(scroll_handle.clone()),
|
||||
} {
|
||||
Scrollbar::new(
|
||||
cx.view().clone().into(),
|
||||
scroll_handle,
|
||||
self.scrollbar_drag_state.clone(),
|
||||
self.delegate.match_count(),
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn hide_scrollbar(&mut self, cx: &mut ViewContext<Self>) {
|
||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||
self.show_scrollbar = false;
|
||||
self.hide_scrollbar_task = Some(cx.spawn(|panel, mut cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(SCROLLBAR_SHOW_INTERVAL)
|
||||
.await;
|
||||
panel
|
||||
.update(&mut cx, |panel, cx| {
|
||||
panel.show_scrollbar = false;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}))
|
||||
}
|
||||
|
||||
fn on_click(&mut self, ix: usize, secondary: bool, cx: &mut ViewContext<Self>) {
|
||||
cx.stop_propagation();
|
||||
cx.prevent_default();
|
||||
|
|
@ -487,7 +543,9 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
|||
let focus_handle = self.focus_handle(cx);
|
||||
|
||||
v_flex()
|
||||
.id("picker")
|
||||
.key_context("Picker")
|
||||
.group("picker-group")
|
||||
.size_full()
|
||||
.track_focus(&focus_handle)
|
||||
.when_some(self.width, |el, width| el.w(width))
|
||||
|
|
@ -499,19 +557,41 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
|||
.on_action(cx.listener(Self::select_last))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_hover(cx.listener(move |this, hovered: &bool, cx| {
|
||||
if *hovered {
|
||||
this.show_scrollbar = true;
|
||||
this.hide_scrollbar_task.take();
|
||||
cx.notify();
|
||||
} else if !focus_handle.is_focused(cx) {
|
||||
this.hide_scrollbar(cx);
|
||||
}
|
||||
}))
|
||||
// Render Query Input header
|
||||
.child(match self.query_input {
|
||||
Some(ref input) => self.delegate.render_query(input, cx),
|
||||
None => div().child(self.head.clone()),
|
||||
})
|
||||
.when(self.delegate.match_count() > 0, |el| {
|
||||
el.child(
|
||||
.when(self.delegate.match_count() > 0, |this| {
|
||||
this.child(
|
||||
v_flex()
|
||||
.flex_grow()
|
||||
.min_h(px(100.))
|
||||
.when_some(self.max_height, |div, max_h| div.max_h(max_h))
|
||||
.overflow_hidden()
|
||||
.child(self.render_element_container(cx)),
|
||||
.child(self.render_element_container(cx))
|
||||
.children(self.render_scrollbar(cx).map(|bar| {
|
||||
// This for let the scrollbar can render on top of the list container.
|
||||
div()
|
||||
.occlude()
|
||||
.absolute()
|
||||
.h_full()
|
||||
.left_auto()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.w(px(bar.width()))
|
||||
.bottom_0()
|
||||
.child(bar)
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(self.delegate.match_count() == 0, |el| {
|
||||
|
|
|
|||
227
crates/ui/src/scrollbar.rs
Normal file
227
crates/ui/src/scrollbar.rs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
use std::{cell::Cell, ops::Range, rc::Rc};
|
||||
|
||||
use crate::theme::{ActiveTheme, Colorize};
|
||||
use gpui::{
|
||||
fill, point, px, relative, AnyView, Bounds, ContentMask, Element, Hitbox, IntoElement,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, Style,
|
||||
UniformListScrollHandle,
|
||||
};
|
||||
|
||||
const MIN_THUMB_PERCENTAGE_HEIGHT: f64 = 0.03;
|
||||
const THUMB_RADIUS: Pixels = Pixels(5.0);
|
||||
const THUMB_INSET: Pixels = Pixels(0.8);
|
||||
|
||||
pub struct Scrollbar {
|
||||
width: f32,
|
||||
view: AnyView,
|
||||
handle: UniformListScrollHandle,
|
||||
/// This is the state of the scrollbar thumb when it is being dragged.
|
||||
/// It must ref from the parent view.
|
||||
drag_state: Rc<Cell<Option<f32>>>,
|
||||
items_count: usize,
|
||||
thumb: Range<f32>,
|
||||
}
|
||||
|
||||
impl Scrollbar {
|
||||
pub fn new(
|
||||
view: AnyView,
|
||||
handle: UniformListScrollHandle,
|
||||
drag_state: Rc<Cell<Option<f32>>>,
|
||||
items_count: usize,
|
||||
show_scrollbar: bool,
|
||||
) -> Option<Self> {
|
||||
let cloned_handle = handle.clone();
|
||||
|
||||
// Ref from: Zed crates\project_panel\src\project_panel.rs
|
||||
// render_scrollbar method
|
||||
let scroll_state = handle.0.borrow();
|
||||
let last_item_height = scroll_state
|
||||
.last_item_height
|
||||
.filter(|_| show_scrollbar && items_count > 0)?;
|
||||
let list_height = items_count as f64 * last_item_height.0 as f64;
|
||||
let current_offset = scroll_state.base_handle.offset().y.0.min(0.).abs() as f64;
|
||||
let mut percentage = current_offset / list_height;
|
||||
let end_offset: f64 =
|
||||
(current_offset + scroll_state.base_handle.bounds().size.height.0 as f64) / list_height;
|
||||
let overshoot = (end_offset - 1.).clamp(0., 1.);
|
||||
if overshoot > 0. {
|
||||
percentage -= overshoot;
|
||||
}
|
||||
|
||||
if percentage + MIN_THUMB_PERCENTAGE_HEIGHT > 1.0 || end_offset > list_height {
|
||||
return None;
|
||||
}
|
||||
if list_height < scroll_state.base_handle.bounds().size.height.0 as f64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let end_offset = end_offset.clamp(percentage + MIN_THUMB_PERCENTAGE_HEIGHT, 1.);
|
||||
|
||||
let thumb = percentage as f32..end_offset as f32;
|
||||
|
||||
Some(Self {
|
||||
view,
|
||||
items_count,
|
||||
width: 12.0,
|
||||
thumb,
|
||||
handle: cloned_handle,
|
||||
drag_state,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.width
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for Scrollbar {
|
||||
type Element = Self;
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScrollbarState {}
|
||||
|
||||
impl Element for Scrollbar {
|
||||
type RequestLayoutState = ScrollbarState;
|
||||
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
fn id(&self) -> Option<gpui::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
cx: &mut gpui::WindowContext,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.flex_grow = 0.0;
|
||||
style.flex_shrink = 1.;
|
||||
style.size.width = px(self.width).into();
|
||||
style.size.height = relative(1.).into();
|
||||
|
||||
(cx.request_layout(style, None), ScrollbarState {})
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
cx.insert_hitbox(bounds, false)
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Option<&gpui::GlobalElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut gpui::WindowContext,
|
||||
) {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
let is_draging = self.drag_state.get().is_some();
|
||||
let bar_bg = cx.theme().scrollbar;
|
||||
let thumb_bg = cx.theme().scrollbar_thumb;
|
||||
let thumb_bg = if is_draging {
|
||||
thumb_bg.darken(0.1)
|
||||
} else {
|
||||
thumb_bg
|
||||
};
|
||||
|
||||
let thumb_top = self.thumb.start * bounds.size.height;
|
||||
let thumb_bottom = self.thumb.end * bounds.size.height;
|
||||
let thumb_percentage_size = self.thumb.end - self.thumb.start;
|
||||
let thumb_bounds = Bounds::from_corners(
|
||||
point(
|
||||
bounds.origin.x + THUMB_INSET,
|
||||
bounds.origin.y + thumb_top + THUMB_INSET,
|
||||
),
|
||||
point(
|
||||
bounds.origin.x + bounds.size.width - (THUMB_INSET * 2),
|
||||
bounds.origin.y + thumb_bottom - (THUMB_INSET * 2),
|
||||
),
|
||||
);
|
||||
|
||||
cx.paint_quad(fill(bounds, bar_bg));
|
||||
cx.paint_quad(fill(thumb_bounds, thumb_bg).corner_radii(THUMB_RADIUS));
|
||||
|
||||
let handle = self.handle.clone();
|
||||
let items_count = self.items_count;
|
||||
|
||||
let drag_state = self.drag_state.clone();
|
||||
cx.on_mouse_event({
|
||||
let scroll = self.handle.clone();
|
||||
move |event: &MouseDownEvent, phase, _cx| {
|
||||
if phase.bubble() && bounds.contains(&event.position) {
|
||||
if !thumb_bounds.contains(&event.position) {
|
||||
let scroll = scroll.0.borrow();
|
||||
if let Some(last_height) = scroll.last_item_height {
|
||||
let max_offset = items_count as f32 * last_height;
|
||||
let percentage =
|
||||
(event.position.y - bounds.origin.y) / bounds.size.height;
|
||||
|
||||
let percentage = percentage.min(1. - thumb_percentage_size);
|
||||
scroll
|
||||
.base_handle
|
||||
.set_offset(point(px(0.), -max_offset * percentage));
|
||||
}
|
||||
} else {
|
||||
let thumb_top_offset =
|
||||
(event.position.y - thumb_bounds.origin.y) / bounds.size.height;
|
||||
drag_state.set(Some(thumb_top_offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.on_mouse_event({
|
||||
let scroll = self.handle.clone();
|
||||
move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase.bubble() && bounds.contains(&event.position) {
|
||||
let scroll = scroll.0.borrow_mut();
|
||||
let current_offset = scroll.base_handle.offset();
|
||||
scroll
|
||||
.base_handle
|
||||
.set_offset(current_offset + event.delta.pixel_delta(cx.line_height()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let view_id = self.view.entity_id();
|
||||
let drag_state = self.drag_state.clone();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, _, cx| {
|
||||
if let Some(drag_state) = drag_state.get().filter(|_| event.dragging()) {
|
||||
let scroll = handle.0.borrow();
|
||||
if let Some(last_height) = scroll.last_item_height {
|
||||
let max_offset = items_count as f32 * last_height;
|
||||
let percentage =
|
||||
(event.position.y - bounds.origin.y) / bounds.size.height - drag_state;
|
||||
|
||||
let percentage = percentage.min(1. - thumb_percentage_size);
|
||||
scroll
|
||||
.base_handle
|
||||
.set_offset(point(px(0.), -max_offset * percentage));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
} else {
|
||||
drag_state.set(None);
|
||||
}
|
||||
});
|
||||
|
||||
let drag_state = self.drag_state.clone();
|
||||
cx.on_mouse_event(move |_event: &MouseUpEvent, phase, cx| {
|
||||
if phase.bubble() {
|
||||
drag_state.set(None);
|
||||
cx.notify(view_id);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -68,13 +68,13 @@ impl Colorize for Hsla {
|
|||
}
|
||||
|
||||
fn lighten(&self, amount: f32) -> Hsla {
|
||||
let l = (self.l + amount).min(1.0);
|
||||
let l = (self.l * (1.0 + amount)).min(1.0);
|
||||
|
||||
Hsla { l, ..*self }
|
||||
}
|
||||
|
||||
fn darken(&self, amount: f32) -> Hsla {
|
||||
let l = (self.l - amount).max(0.0);
|
||||
let l = (self.l * (1.0 - amount)).max(0.0);
|
||||
|
||||
Hsla { l, ..*self }
|
||||
}
|
||||
|
|
@ -104,6 +104,8 @@ struct Colors {
|
|||
pub input: Hsla,
|
||||
pub ring: Hsla,
|
||||
pub selection: Hsla,
|
||||
pub scrollbar: Hsla,
|
||||
pub scrollbar_thumb: Hsla,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
|
|
@ -154,6 +156,8 @@ impl Colors {
|
|||
input: hsl(240.0, 5.9, 90.0),
|
||||
ring: hsl(240.0, 5.9, 10.0),
|
||||
selection: hsl(211.0, 97.0, 85.0),
|
||||
scrollbar: Hsla::transparent_black(),
|
||||
scrollbar_thumb: hsl(240.0, 5.9, 90.0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -203,6 +207,8 @@ impl Colors {
|
|||
input: hsl(240.0, 3.7, 15.9),
|
||||
ring: hsl(240.0, 4.9, 83.9),
|
||||
selection: hsl(211.0, 97.0, 85.0),
|
||||
scrollbar: Hsla::transparent_black(),
|
||||
scrollbar_thumb: hsl(240.0, 3.7, 15.9),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -234,6 +240,8 @@ pub struct Theme {
|
|||
pub input: Hsla,
|
||||
pub ring: Hsla,
|
||||
pub selection: Hsla,
|
||||
pub scrollbar: Hsla,
|
||||
pub scrollbar_thumb: Hsla,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +280,8 @@ impl From<Colors> for Theme {
|
|||
border: colors.border,
|
||||
input: colors.input,
|
||||
ring: colors.ring,
|
||||
scrollbar: colors.scrollbar,
|
||||
scrollbar_thumb: colors.scrollbar_thumb,
|
||||
selection: colors.selection,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue