mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 10:57:22 +03:00
Merge branch 'gpui2' into gpui2-ui
This commit is contained in:
commit
5491398a64
@ -155,8 +155,9 @@ impl Refineable for TextStyleRefinement {
|
||||
}
|
||||
}
|
||||
|
||||
fn refined(self, refinement: Self::Refinement) -> Self {
|
||||
todo!()
|
||||
fn refined(mut self, refinement: Self::Refinement) -> Self {
|
||||
self.refine(&refinement);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
25
crates/gpui3/src/active.rs
Normal file
25
crates/gpui3/src/active.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::{SharedString, StyleRefinement};
|
||||
|
||||
pub trait Active {
|
||||
fn set_active_style(&mut self, group_name: Option<SharedString>, style: StyleRefinement);
|
||||
|
||||
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.set_active_style(None, f(StyleRefinement::default()));
|
||||
self
|
||||
}
|
||||
|
||||
fn group_active(
|
||||
mut self,
|
||||
group_name: impl Into<SharedString>,
|
||||
f: impl FnOnce(StyleRefinement) -> StyleRefinement,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.set_active_style(Some(group_name.into()), f(StyleRefinement::default()));
|
||||
self
|
||||
}
|
||||
}
|
@ -1,9 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
BorrowWindow, Bounds, Clickable, ElementId, Group, LayoutId, MouseDownEvent, MouseUpEvent,
|
||||
Pixels, Point, SharedString, ViewContext,
|
||||
};
|
||||
use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, ViewContext};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
|
||||
@ -11,7 +6,7 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
|
||||
type ViewState: 'static + Send + Sync;
|
||||
type ElementState: 'static + Send + Sync;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId>;
|
||||
fn id(&self) -> Option<ElementId>;
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
@ -27,46 +22,34 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
);
|
||||
|
||||
fn group(self, name: impl Into<SharedString>) -> Group<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Group::new(name.into(), self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IdentifiedElement: Element {
|
||||
fn element_id(&self) -> ElementId {
|
||||
Element::element_id(self).unwrap()
|
||||
}
|
||||
|
||||
fn on_click(
|
||||
self,
|
||||
listener: impl Fn(
|
||||
&mut Self::ViewState,
|
||||
(&MouseDownEvent, &MouseUpEvent),
|
||||
&mut ViewContext<Self::ViewState>,
|
||||
) + Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Clickable<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Clickable::new(self, Arc::from(listener))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 8]>);
|
||||
|
||||
pub trait ParentElement {
|
||||
type State;
|
||||
pub trait ElementIdentity: 'static + Send + Sync {
|
||||
fn id(&self) -> Option<ElementId>;
|
||||
}
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]>;
|
||||
pub struct IdentifiedElement(pub(crate) ElementId);
|
||||
pub struct AnonymousElement;
|
||||
|
||||
fn child(mut self, child: impl IntoAnyElement<Self::State>) -> Self
|
||||
impl ElementIdentity for IdentifiedElement {
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementIdentity for AnonymousElement {
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ParentElement: Element {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]>;
|
||||
|
||||
fn child(mut self, child: impl IntoAnyElement<Self::ViewState>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -74,7 +57,10 @@ pub trait ParentElement {
|
||||
self
|
||||
}
|
||||
|
||||
fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<Self::State>>) -> Self
|
||||
fn children(
|
||||
mut self,
|
||||
iter: impl IntoIterator<Item = impl IntoAnyElement<Self::ViewState>>,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@ -126,7 +112,7 @@ impl<E: Element> RenderedElement<E> {
|
||||
frame_state: &mut Option<E::ElementState>,
|
||||
cx: &mut ViewContext<E::ViewState>,
|
||||
) {
|
||||
if let Some(id) = self.element.element_id() {
|
||||
if let Some(id) = self.element.id() {
|
||||
cx.with_element_state(id, |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap();
|
||||
self.element
|
||||
@ -146,7 +132,7 @@ where
|
||||
S: 'static + Send + Sync,
|
||||
{
|
||||
fn layout(&mut self, state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) -> LayoutId {
|
||||
let (layout_id, frame_state) = if let Some(id) = self.element.element_id() {
|
||||
let (layout_id, frame_state) = if let Some(id) = self.element.id() {
|
||||
let layout_id = cx.with_element_state(id, |element_state, cx| {
|
||||
self.element.layout(state, element_state, cx)
|
||||
});
|
||||
|
@ -1,20 +1,9 @@
|
||||
mod clickable;
|
||||
mod div;
|
||||
mod group;
|
||||
mod hoverable;
|
||||
mod identified;
|
||||
mod img;
|
||||
mod nested;
|
||||
mod pressable;
|
||||
mod svg;
|
||||
mod text;
|
||||
|
||||
pub use clickable::*;
|
||||
pub use div::*;
|
||||
pub use group::*;
|
||||
pub use hoverable::*;
|
||||
pub use identified::*;
|
||||
pub use img::*;
|
||||
pub use pressable::*;
|
||||
pub use svg::*;
|
||||
pub use text::*;
|
||||
|
@ -1,144 +0,0 @@
|
||||
use crate::{
|
||||
AnyElement, Bounds, DispatchPhase, Element, IdentifiedElement, Interactive, IntoAnyElement,
|
||||
MouseDownEvent, MouseEventListeners, MouseUpEvent, ParentElement, Pixels, Styled, ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use refineable::Cascade;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type ClickListener<S> =
|
||||
dyn Fn(&mut S, (&MouseDownEvent, &MouseUpEvent), &mut ViewContext<S>) + Send + Sync + 'static;
|
||||
|
||||
pub struct Clickable<E: Element> {
|
||||
child: E,
|
||||
listener: Arc<ClickListener<E::ViewState>>,
|
||||
}
|
||||
|
||||
pub struct ClickableState<S> {
|
||||
last_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
|
||||
child_state: S,
|
||||
}
|
||||
|
||||
impl<E: Element> Clickable<E> {
|
||||
pub fn new(child: E, listener: Arc<ClickListener<E::ViewState>>) -> Self {
|
||||
Self { child, listener }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Styled for Clickable<E>
|
||||
where
|
||||
E: Styled + IdentifiedElement,
|
||||
{
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.child.declared_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, E> Interactive<S> for Clickable<E>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
E: IdentifiedElement + Interactive<S>,
|
||||
{
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S> {
|
||||
self.child.listeners()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: IdentifiedElement> IntoAnyElement<E::ViewState> for Clickable<E> {
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Element for Clickable<E>
|
||||
where
|
||||
E: IdentifiedElement,
|
||||
{
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = ClickableState<E::ElementState>;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
Some(IdentifiedElement::element_id(&self.child))
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
if let Some(element_state) = element_state {
|
||||
let (layout_id, child_state) =
|
||||
self.child
|
||||
.layout(state, Some(element_state.child_state), cx);
|
||||
|
||||
let element_state = ClickableState {
|
||||
last_mouse_down: element_state.last_mouse_down,
|
||||
child_state,
|
||||
};
|
||||
(layout_id, element_state)
|
||||
} else {
|
||||
let (layout_id, child_state) = self.child.layout(state, None, cx);
|
||||
let element_state = ClickableState {
|
||||
last_mouse_down: Default::default(),
|
||||
child_state,
|
||||
};
|
||||
(layout_id, element_state)
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
let last_mouse_down = element_state.last_mouse_down.clone();
|
||||
let is_some = last_mouse_down.lock().is_some();
|
||||
|
||||
if is_some {
|
||||
let listener = self.listener.clone();
|
||||
cx.on_mouse_event(move |view, up_event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture && !bounds.contains_point(up_event.position) {
|
||||
*last_mouse_down.lock() = None;
|
||||
} else if phase == DispatchPhase::Bubble && bounds.contains_point(up_event.position)
|
||||
{
|
||||
if let Some(down_event) = last_mouse_down.lock().take() {
|
||||
listener(view, (&down_event, up_event), cx);
|
||||
} else {
|
||||
log::error!("No mouse down event found for click event");
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
cx.on_mouse_event(move |_, event: &MouseDownEvent, phase, _| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
if bounds.contains_point(event.position) {
|
||||
*last_mouse_down.lock() = Some(event.clone());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.child
|
||||
.paint(bounds, state, &mut element_state.child_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: IdentifiedElement + ParentElement> ParentElement for Clickable<E> {
|
||||
type State = E::State;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IdentifiedElement for Clickable<E> where E: IdentifiedElement + Styled {}
|
@ -1,227 +1,42 @@
|
||||
use crate::{
|
||||
AnyElement, BorrowWindow, Bounds, Cascade, Element, ElementId, IdentifiedElement, Interactive,
|
||||
IntoAnyElement, LayoutId, MouseEventListeners, Overflow, ParentElement, Pixels, Point,
|
||||
Refineable, Style, Styled, ViewContext,
|
||||
Active, AnonymousElement, AnyElement, AppContext, BorrowWindow, Bounds, Click, DispatchPhase,
|
||||
Element, ElementId, ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement,
|
||||
LayoutId, MouseClickEvent, MouseDownEvent, MouseEventListeners, MouseMoveEvent, MouseUpEvent,
|
||||
Overflow, ParentElement, Pixels, Point, ScrollWheelEvent, SharedString, Style, StyleRefinement,
|
||||
Styled, ViewContext,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
use refineable::Refineable;
|
||||
use smallvec::SmallVec;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub enum HasId {}
|
||||
|
||||
pub struct Div<S: 'static, I = ()> {
|
||||
styles: Cascade<Style>,
|
||||
id: Option<ElementId>,
|
||||
listeners: MouseEventListeners<S>,
|
||||
children: SmallVec<[AnyElement<S>; 2]>,
|
||||
scroll_state: Option<ScrollState>,
|
||||
identified: PhantomData<I>,
|
||||
#[derive(Default)]
|
||||
pub struct DivState {
|
||||
active_state: Arc<Mutex<ActiveState>>,
|
||||
pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
|
||||
}
|
||||
|
||||
pub fn div<S>() -> Div<S> {
|
||||
Div {
|
||||
styles: Default::default(),
|
||||
id: None,
|
||||
listeners: Default::default(),
|
||||
children: Default::default(),
|
||||
scroll_state: None,
|
||||
identified: PhantomData,
|
||||
#[derive(Copy, Clone, Default, Eq, PartialEq)]
|
||||
struct ActiveState {
|
||||
group: bool,
|
||||
element: bool,
|
||||
}
|
||||
|
||||
impl ActiveState {
|
||||
pub fn is_none(&self) -> bool {
|
||||
!self.group && !self.element
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Marker> IntoAnyElement<S> for Div<S, Marker>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
Marker: 'static + Send + Sync,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
|
||||
|
||||
impl<S, Marker> Element for Div<S, Marker>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
Marker: 'static + Send + Sync,
|
||||
{
|
||||
type ViewState = S;
|
||||
type ElementState = ();
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view: &mut S,
|
||||
_: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> (LayoutId, Self::ElementState) {
|
||||
let style = self.computed_style();
|
||||
let child_layout_ids = style.apply_text_style(cx, |cx| {
|
||||
self.with_element_id(cx, |this, cx| this.layout_children(view, cx))
|
||||
});
|
||||
let layout_id = cx.request_layout(style.into(), child_layout_ids.clone());
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut S,
|
||||
_: &mut (),
|
||||
cx: &mut ViewContext<S>,
|
||||
) {
|
||||
let style = self.computed_style();
|
||||
let z_index = style.z_index.unwrap_or(0);
|
||||
cx.stack(z_index, |cx| style.paint(bounds, cx));
|
||||
|
||||
let overflow = &style.overflow;
|
||||
|
||||
style.apply_text_style(cx, |cx| {
|
||||
cx.stack(z_index + 1, |cx| {
|
||||
style.apply_overflow(bounds, cx, |cx| {
|
||||
self.with_element_id(cx, |this, cx| {
|
||||
this.listeners.paint(bounds, cx);
|
||||
this.paint_children(overflow, state, cx)
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Div<S, ()>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
{
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Div<S, HasId> {
|
||||
Div {
|
||||
styles: self.styles,
|
||||
id: Some(id.into()),
|
||||
listeners: self.listeners,
|
||||
children: self.children,
|
||||
scroll_state: self.scroll_state,
|
||||
identified: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Marker> Div<S, Marker>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
Marker: 'static + Send + Sync,
|
||||
{
|
||||
pub fn z_index(mut self, z_index: u32) -> Self {
|
||||
self.declared_style().z_index = Some(z_index);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden(mut self) -> Self {
|
||||
self.declared_style().overflow.x = Some(Overflow::Hidden);
|
||||
self.declared_style().overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_x(mut self) -> Self {
|
||||
self.declared_style().overflow.x = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_y(mut self) -> Self {
|
||||
self.declared_style().overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.x = Some(Overflow::Scroll);
|
||||
self.declared_style().overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.x = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
|
||||
self.scroll_state = Some(scroll_state);
|
||||
self.declared_style().overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
fn scroll_offset(&self, overflow: &Point<Overflow>) -> Point<Pixels> {
|
||||
let mut offset = Point::default();
|
||||
if overflow.y == Overflow::Scroll {
|
||||
offset.y = self.scroll_state.as_ref().unwrap().y();
|
||||
}
|
||||
if overflow.x == Overflow::Scroll {
|
||||
offset.x = self.scroll_state.as_ref().unwrap().x();
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
fn layout_children(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> Vec<LayoutId> {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.map(|child| child.layout(view, cx))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn paint_children(
|
||||
&mut self,
|
||||
overflow: &Point<Overflow>,
|
||||
state: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) {
|
||||
let scroll_offset = self.scroll_offset(overflow);
|
||||
for child in &mut self.children {
|
||||
child.paint(state, Some(scroll_offset), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn with_element_id<R>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<S>,
|
||||
f: impl FnOnce(&mut Self, &mut ViewContext<S>) -> R,
|
||||
) -> R {
|
||||
if let Some(element_id) = self.element_id() {
|
||||
cx.with_element_id(element_id, |cx| f(self, cx))
|
||||
} else {
|
||||
f(self, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + Send + Sync, Marker: 'static + Send + Sync> Styled for Div<V, Marker> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<Self::Style> {
|
||||
&mut self.styles
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
self.styles.base()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Send + Sync + 'static> IdentifiedElement for Div<V, HasId> {}
|
||||
|
||||
impl<V: Send + Sync + 'static, Marker: 'static + Send + Sync> Interactive<V> for Div<V, Marker> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<V> {
|
||||
&mut self.listeners
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, Marker: 'static + Send + Sync> ParentElement for Div<V, Marker> {
|
||||
type State = V;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
pub fn group_bounds(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
|
||||
cx.default_global::<GroupBounds>()
|
||||
.0
|
||||
.get(name)
|
||||
.and_then(|bounds_stack| bounds_stack.last().cloned())
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@ -244,3 +59,436 @@ impl ScrollState {
|
||||
self.0.lock().y = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn div<S>() -> Div<S, AnonymousElement>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
{
|
||||
Div {
|
||||
kind: AnonymousElement,
|
||||
children: SmallVec::new(),
|
||||
group: None,
|
||||
base_style: StyleRefinement::default(),
|
||||
hover_style: StyleRefinement::default(),
|
||||
group_hover: None,
|
||||
active_style: StyleRefinement::default(),
|
||||
group_active: None,
|
||||
listeners: MouseEventListeners::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Div<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
|
||||
kind: K,
|
||||
children: SmallVec<[AnyElement<V>; 2]>,
|
||||
group: Option<SharedString>,
|
||||
base_style: StyleRefinement,
|
||||
hover_style: StyleRefinement,
|
||||
group_hover: Option<GroupStyle>,
|
||||
active_style: StyleRefinement,
|
||||
group_active: Option<GroupStyle>,
|
||||
listeners: MouseEventListeners<V>,
|
||||
}
|
||||
|
||||
struct GroupStyle {
|
||||
group: SharedString,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
impl<V> Div<V, AnonymousElement>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Div<V, IdentifiedElement> {
|
||||
Div {
|
||||
kind: IdentifiedElement(id.into()),
|
||||
children: self.children,
|
||||
group: self.group,
|
||||
base_style: self.base_style,
|
||||
hover_style: self.hover_style,
|
||||
group_hover: self.group_hover,
|
||||
active_style: self.active_style,
|
||||
group_active: self.group_active,
|
||||
listeners: self.listeners,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
|
||||
self.group = Some(group.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn z_index(mut self, z_index: u32) -> Self {
|
||||
self.base_style.z_index = Some(z_index);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden(mut self) -> Self {
|
||||
self.base_style.overflow.x = Some(Overflow::Hidden);
|
||||
self.base_style.overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_x(mut self) -> Self {
|
||||
self.base_style.overflow.x = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_hidden_y(mut self) -> Self {
|
||||
self.base_style.overflow.y = Some(Overflow::Hidden);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_scroll(mut self, _scroll_state: ScrollState) -> Self {
|
||||
// todo!("impl scrolling")
|
||||
// self.scroll_state = Some(scroll_state);
|
||||
self.base_style.overflow.x = Some(Overflow::Scroll);
|
||||
self.base_style.overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_x_scroll(mut self, _scroll_state: ScrollState) -> Self {
|
||||
// todo!("impl scrolling")
|
||||
// self.scroll_state = Some(scroll_state);
|
||||
self.base_style.overflow.x = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn overflow_y_scroll(mut self, _scroll_state: ScrollState) -> Self {
|
||||
// todo!("impl scrolling")
|
||||
// self.scroll_state = Some(scroll_state);
|
||||
self.base_style.overflow.y = Some(Overflow::Scroll);
|
||||
self
|
||||
}
|
||||
|
||||
fn with_element_id<R>(
|
||||
&mut self,
|
||||
cx: &mut ViewContext<V>,
|
||||
f: impl FnOnce(&mut Self, &mut ViewContext<V>) -> R,
|
||||
) -> R {
|
||||
if let Some(id) = self.id() {
|
||||
cx.with_element_id(id, |cx| f(self, cx))
|
||||
} else {
|
||||
f(self, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_style(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &DivState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Style {
|
||||
let mut computed_style = Style::default();
|
||||
computed_style.refine(&self.base_style);
|
||||
|
||||
let mouse_position = cx.mouse_position();
|
||||
|
||||
if let Some(group_hover) = self.group_hover.as_ref() {
|
||||
if let Some(group_bounds) = group_bounds(&group_hover.group, cx) {
|
||||
if group_bounds.contains_point(&mouse_position) {
|
||||
computed_style.refine(&group_hover.style);
|
||||
}
|
||||
}
|
||||
}
|
||||
if bounds.contains_point(&mouse_position) {
|
||||
computed_style.refine(&self.hover_style);
|
||||
}
|
||||
|
||||
let active_state = *state.active_state.lock();
|
||||
if active_state.group {
|
||||
if let Some(GroupStyle { style, .. }) = self.group_active.as_ref() {
|
||||
computed_style.refine(style);
|
||||
}
|
||||
}
|
||||
if active_state.element {
|
||||
computed_style.refine(&self.active_style);
|
||||
}
|
||||
|
||||
computed_style
|
||||
}
|
||||
|
||||
fn paint_hover_listeners(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
group_bounds: Option<Bounds<Pixels>>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
if let Some(group_bounds) = group_bounds {
|
||||
paint_hover_listener(group_bounds, cx);
|
||||
}
|
||||
|
||||
if self.hover_style.is_some() {
|
||||
paint_hover_listener(bounds, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_active_listener(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
group_bounds: Option<Bounds<Pixels>>,
|
||||
active_state: Arc<Mutex<ActiveState>>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
if active_state.lock().is_none() {
|
||||
cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
let group =
|
||||
group_bounds.map_or(false, |bounds| bounds.contains_point(&down.position));
|
||||
let element = bounds.contains_point(&down.position);
|
||||
if group || element {
|
||||
*active_state.lock() = ActiveState { group, element };
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
*active_state.lock() = ActiveState::default();
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_event_listeners(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let click_listeners = self.listeners.mouse_click.clone();
|
||||
let mouse_down = pending_click.lock().clone();
|
||||
if let Some(mouse_down) = mouse_down {
|
||||
cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
let mouse_click = MouseClickEvent {
|
||||
down: mouse_down.clone(),
|
||||
up: event.clone(),
|
||||
};
|
||||
for listener in &click_listeners {
|
||||
listener(state, &mouse_click, cx);
|
||||
}
|
||||
}
|
||||
|
||||
*pending_click.lock() = None;
|
||||
});
|
||||
} else {
|
||||
cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
*pending_click.lock() = Some(event.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for listener in self.listeners.mouse_down.iter().cloned() {
|
||||
cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
|
||||
listener(state, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for listener in self.listeners.mouse_up.iter().cloned() {
|
||||
cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
|
||||
listener(state, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for listener in self.listeners.mouse_move.iter().cloned() {
|
||||
cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
|
||||
listener(state, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for listener in self.listeners.scroll_wheel.iter().cloned() {
|
||||
cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
|
||||
listener(state, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Element for Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
type ViewState = V;
|
||||
type ElementState = DivState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
self.kind.id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
view_state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (LayoutId, Self::ElementState) {
|
||||
let element_state = element_state.unwrap_or_default();
|
||||
let style = self.compute_style(Bounds::default(), &element_state, cx);
|
||||
style.apply_text_style(cx, |cx| {
|
||||
self.with_element_id(cx, |this, cx| {
|
||||
let layout_ids = this
|
||||
.children
|
||||
.iter_mut()
|
||||
.map(|child| child.layout(view_state, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let layout_id = cx.request_layout(&style, layout_ids);
|
||||
(layout_id, element_state)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
view_state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
self.with_element_id(cx, |this, cx| {
|
||||
if let Some(group) = this.group.clone() {
|
||||
cx.default_global::<GroupBounds>()
|
||||
.0
|
||||
.entry(group)
|
||||
.or_default()
|
||||
.push(bounds);
|
||||
}
|
||||
|
||||
let hover_group_bounds = this
|
||||
.group_hover
|
||||
.as_ref()
|
||||
.and_then(|group_hover| group_bounds(&group_hover.group, cx));
|
||||
let active_group_bounds = this
|
||||
.group_active
|
||||
.as_ref()
|
||||
.and_then(|group_active| group_bounds(&group_active.group, cx));
|
||||
let style = this.compute_style(bounds, element_state, cx);
|
||||
let z_index = style.z_index.unwrap_or(0);
|
||||
|
||||
// Paint background and event handlers.
|
||||
cx.stack(z_index, |cx| {
|
||||
cx.stack(0, |cx| {
|
||||
style.paint(bounds, cx);
|
||||
this.paint_hover_listeners(bounds, hover_group_bounds, cx);
|
||||
this.paint_active_listener(
|
||||
bounds,
|
||||
active_group_bounds,
|
||||
element_state.active_state.clone(),
|
||||
cx,
|
||||
);
|
||||
this.paint_event_listeners(bounds, element_state.pending_click.clone(), cx);
|
||||
});
|
||||
|
||||
cx.stack(1, |cx| {
|
||||
style.apply_text_style(cx, |cx| {
|
||||
style.apply_overflow(bounds, cx, |cx| {
|
||||
for child in &mut this.children {
|
||||
child.paint(view_state, None, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
if let Some(group) = this.group.as_ref() {
|
||||
cx.default_global::<GroupBounds>()
|
||||
.0
|
||||
.get_mut(group)
|
||||
.unwrap()
|
||||
.pop();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> IntoAnyElement<V> for Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> ParentElement for Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Styled for Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.base_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Interactive for Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<V> {
|
||||
&mut self.listeners
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Hover for Div<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
|
||||
if let Some(group) = group {
|
||||
self.group_hover = Some(GroupStyle { group, style });
|
||||
} else {
|
||||
self.hover_style = style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Click for Div<V, IdentifiedElement> where V: 'static + Send + Sync {}
|
||||
|
||||
impl<V> Active for Div<V, IdentifiedElement>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
|
||||
if let Some(group) = group {
|
||||
self.group_active = Some(GroupStyle { group, style });
|
||||
} else {
|
||||
self.active_style = style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_hover_listener<V>(bounds: Bounds<Pixels>, cx: &mut ViewContext<V>)
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
let hovered = bounds.contains_point(&cx.mouse_position());
|
||||
cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
if bounds.contains_point(&event.position) != hovered {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,103 +0,0 @@
|
||||
use crate::{
|
||||
AnyElement, AppContext, Bounds, Element, ElementId, IdentifiedElement, Interactive,
|
||||
IntoAnyElement, MouseEventListeners, ParentElement, Pixels, SharedString, Styled, ViewContext,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use refineable::Cascade;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(Default)]
|
||||
struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
|
||||
|
||||
pub fn group_bounds(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
|
||||
cx.default_global::<GroupBounds>()
|
||||
.0
|
||||
.get(name)
|
||||
.and_then(|bounds_stack| bounds_stack.last().cloned())
|
||||
}
|
||||
|
||||
pub struct Group<E> {
|
||||
name: SharedString,
|
||||
child: E,
|
||||
}
|
||||
|
||||
impl<E> Group<E> {
|
||||
pub fn new(name: SharedString, child: E) -> Self {
|
||||
Group { name, child }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> IntoAnyElement<E::ViewState> for Group<E> {
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> Element for Group<E> {
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = E::ElementState;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
self.child.element_id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
self.child.layout(state, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
cx.default_global::<GroupBounds>()
|
||||
.0
|
||||
.entry(self.name.clone())
|
||||
.or_default()
|
||||
.push(bounds);
|
||||
self.child.paint(bounds, state, element_state, cx);
|
||||
cx.default_global::<GroupBounds>()
|
||||
.0
|
||||
.get_mut(&self.name)
|
||||
.unwrap()
|
||||
.pop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: ParentElement> ParentElement for Group<E> {
|
||||
type State = E::State;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IdentifiedElement for Group<E> where E: IdentifiedElement {}
|
||||
|
||||
impl<E> Styled for Group<E>
|
||||
where
|
||||
E: Styled,
|
||||
{
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.child.declared_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Group<E> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S> {
|
||||
self.child.listeners()
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
use crate::{
|
||||
group_bounds, AnyElement, Bounds, DispatchPhase, Element, ElementId, IdentifiedElement,
|
||||
Interactive, IntoAnyElement, MouseEventListeners, MouseMoveEvent, ParentElement, Pixels,
|
||||
SharedString, Styled, ViewContext,
|
||||
};
|
||||
use refineable::{Cascade, CascadeSlot, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
};
|
||||
|
||||
pub struct Hoverable<E: Styled> {
|
||||
group: Option<SharedString>,
|
||||
hovered: Arc<AtomicBool>,
|
||||
cascade_slot: CascadeSlot,
|
||||
hovered_style: <E::Style as Refineable>::Refinement,
|
||||
child: E,
|
||||
}
|
||||
|
||||
impl<E: Styled> Hoverable<E> {
|
||||
pub fn new(mut child: E, hover_group: Option<SharedString>) -> Self {
|
||||
Self {
|
||||
group: hover_group,
|
||||
hovered: Arc::new(AtomicBool::new(false)),
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
hovered_style: Default::default(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Styled for Hoverable<E>
|
||||
where
|
||||
E: Styled,
|
||||
{
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
&mut self.hovered_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Hoverable<E> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S> {
|
||||
self.child.listeners()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoAnyElement<E::ViewState> for Hoverable<E>
|
||||
where
|
||||
E: Element + Styled,
|
||||
<E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
|
||||
<<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Element for Hoverable<E>
|
||||
where
|
||||
E: Element + Styled,
|
||||
<E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
|
||||
<<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
|
||||
{
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = E::ElementState;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
self.child.element_id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
self.child.layout(state, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
let target_bounds = self
|
||||
.group
|
||||
.as_ref()
|
||||
.and_then(|group| group_bounds(group, cx))
|
||||
.unwrap_or(bounds);
|
||||
|
||||
let hovered = target_bounds.contains_point(cx.mouse_position());
|
||||
|
||||
let slot = self.cascade_slot;
|
||||
let style = hovered.then_some(self.hovered_style.clone());
|
||||
self.style_cascade().set(slot, style);
|
||||
self.hovered.store(hovered, SeqCst);
|
||||
|
||||
cx.on_mouse_event({
|
||||
let hovered = self.hovered.clone();
|
||||
|
||||
move |_, event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
if target_bounds.contains_point(event.position) != hovered.load(SeqCst) {
|
||||
cx.notify();
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.child.paint(bounds, state, element_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: ParentElement + Styled> ParentElement for Hoverable<E> {
|
||||
type State = E::State;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IdentifiedElement for Hoverable<E>
|
||||
where
|
||||
E: IdentifiedElement + Styled,
|
||||
<E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
|
||||
<<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
|
||||
{
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use refineable::{Cascade, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
AnyElement, BorrowWindow, Bounds, Element, ElementId, IdentifiedElement, IntoAnyElement,
|
||||
LayoutId, ParentElement, Styled, ViewContext,
|
||||
};
|
||||
|
||||
pub struct Identified<E> {
|
||||
pub(crate) element: E,
|
||||
pub(crate) id: ElementId,
|
||||
}
|
||||
|
||||
impl<E: Element> IntoAnyElement<E::ViewState> for Identified<E> {
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> Element for Identified<E> {
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = E::ElementState;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (LayoutId, Self::ElementState) {
|
||||
self.element.layout(state, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
cx.with_element_id(self.id.clone(), |cx| {
|
||||
self.element.paint(bounds, state, element_state, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> IdentifiedElement for Identified<E> {}
|
||||
|
||||
impl<E: Styled> Styled for Identified<E> {
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<Self::Style> {
|
||||
self.element.style_cascade()
|
||||
}
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
|
||||
self.element.declared_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: ParentElement> ParentElement for Identified<E> {
|
||||
type State = E::State;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
self.element.children_mut()
|
||||
}
|
||||
}
|
@ -1,29 +1,33 @@
|
||||
use crate::{
|
||||
AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Pixels, SharedString,
|
||||
Style, Styled, ViewContext,
|
||||
div, Active, AnonymousElement, AnyElement, BorrowWindow, Bounds, Click, Div, DivState, Element,
|
||||
ElementId, ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement, LayoutId,
|
||||
MouseEventListeners, Pixels, SharedString, StyleRefinement, Styled, ViewContext,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use refineable::Cascade;
|
||||
use std::marker::PhantomData;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct Img<S> {
|
||||
style: Cascade<Style>,
|
||||
pub struct Img<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
|
||||
base: Div<V, K>,
|
||||
uri: Option<SharedString>,
|
||||
grayscale: bool,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
pub fn img<S>() -> Img<S> {
|
||||
pub fn img<V>() -> Img<V, AnonymousElement>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
Img {
|
||||
style: Cascade::default(),
|
||||
base: div(),
|
||||
uri: None,
|
||||
grayscale: false,
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Img<S> {
|
||||
impl<V, K> Img<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
|
||||
self.uri = Some(uri.into());
|
||||
self
|
||||
@ -35,47 +39,63 @@ impl<S> Img<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoAnyElement<S> for Img<S>
|
||||
impl<V: 'static + Send + Sync> Img<V, AnonymousElement> {
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Img<V, IdentifiedElement> {
|
||||
Img {
|
||||
base: self.base.id(id),
|
||||
uri: self.uri,
|
||||
grayscale: self.grayscale,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> IntoAnyElement<V> for Img<V, K>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Send + Sync + 'static> Element for Img<S> {
|
||||
type ViewState = S;
|
||||
type ElementState = ();
|
||||
impl<V, K> Element for Img<V, K>
|
||||
where
|
||||
V: Send + Sync + 'static,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
type ViewState = V;
|
||||
type ElementState = DivState;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
self.base.id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut Self::ViewState,
|
||||
_: Option<Self::ElementState>,
|
||||
view_state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (LayoutId, Self::ElementState)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
let layout_id = cx.request_layout(style, []);
|
||||
(layout_id, ())
|
||||
self.base.layout(view_state, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::ViewState,
|
||||
_: &mut Self::ElementState,
|
||||
view: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
let style = self.computed_style();
|
||||
cx.stack(0, |cx| {
|
||||
self.base.paint(bounds, view, element_state, cx);
|
||||
});
|
||||
|
||||
style.paint(bounds, cx);
|
||||
let style = self.base.compute_style(bounds, element_state, cx);
|
||||
let corner_radii = style.corner_radii;
|
||||
|
||||
if let Some(uri) = self.uri.clone() {
|
||||
let image_future = cx.image_cache.get(uri);
|
||||
@ -84,7 +104,7 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
|
||||
.now_or_never()
|
||||
.and_then(ResultExt::log_err)
|
||||
{
|
||||
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
cx.stack(1, |cx| {
|
||||
cx.paint_image(bounds, corner_radii, data, self.grayscale)
|
||||
.log_err()
|
||||
@ -101,14 +121,43 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Styled for Img<S> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<Self::Style> {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.style.base()
|
||||
impl<V, K> Styled for Img<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Interactive for Img<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<V> {
|
||||
self.base.listeners()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Hover for Img<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
|
||||
self.base.set_hover_style(group, style);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Click for Img<V, IdentifiedElement> where V: 'static + Send + Sync {}
|
||||
|
||||
impl<V> Active for Img<V, IdentifiedElement>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
|
||||
self.base.set_active_style(group, style)
|
||||
}
|
||||
}
|
||||
|
@ -1,258 +0,0 @@
|
||||
use crate::{
|
||||
group_bounds, AnyElement, DispatchPhase, Element, IntoAnyElement, MouseMoveEvent, SharedString,
|
||||
Style, StyleCascade, StyleRefinement,
|
||||
};
|
||||
use refineable::{CascadeSlot, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
};
|
||||
|
||||
trait LayoutNode<V: 'static + Send + Sync> {
|
||||
fn state(&mut self) -> &mut LayoutNodeState<V>;
|
||||
|
||||
fn child(mut self, child: impl IntoAnyElement<V>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.state().children.push(child.into_any());
|
||||
self
|
||||
}
|
||||
|
||||
fn children<C, E>(mut self, children: C) -> Self
|
||||
where
|
||||
C: IntoIterator<Item = E>,
|
||||
E: IntoAnyElement<V>,
|
||||
Self: Sized,
|
||||
{
|
||||
for child in children {
|
||||
self.state().children.push(child.into_any());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutNodeState<V: 'static + Send + Sync> {
|
||||
style_cascade: StyleCascade,
|
||||
computed_style: Option<Style>,
|
||||
children: SmallVec<[AnyElement<V>; 2]>,
|
||||
}
|
||||
|
||||
impl<V> IntoAnyElement<V> for LayoutNodeState<V>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + Send + Sync> Element for LayoutNodeState<V> {
|
||||
type ViewState = V;
|
||||
type ElementState = ();
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
_: Option<Self::ElementState>,
|
||||
cx: &mut crate::ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
let layout_ids = self
|
||||
.children
|
||||
.iter_mut()
|
||||
.map(|child| child.layout(state, cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// todo!("pass just the style cascade")
|
||||
let style = self.computed_style().clone();
|
||||
let layout_id = cx.request_layout(style, layout_ids);
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: crate::Bounds<crate::Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
_: &mut Self::ElementState,
|
||||
cx: &mut crate::ViewContext<Self::ViewState>,
|
||||
) {
|
||||
for child in &mut self.children {
|
||||
child.paint(state, None, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Styled {
|
||||
fn style_cascade(&mut self) -> &mut StyleCascade;
|
||||
fn computed_style(&mut self) -> &Style;
|
||||
}
|
||||
|
||||
pub struct StyledElement<E> {
|
||||
child: E,
|
||||
}
|
||||
|
||||
impl<E> IntoAnyElement<E::ViewState> for StyledElement<E>
|
||||
where
|
||||
E: Element + Styled,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element + Styled> Element for StyledElement<E> {
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = E::ElementState;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
self.child.element_id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut crate::ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
self.child.layout(state, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut crate::ViewContext<Self::ViewState>,
|
||||
) {
|
||||
self.child.computed_style().paint(bounds, cx);
|
||||
self.child.paint(bounds, state, element_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Hoverable {
|
||||
fn hover_style(&mut self) -> &mut StyleRefinement;
|
||||
|
||||
fn hover(mut self, f: impl FnOnce(&mut StyleRefinement) -> &mut StyleRefinement) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
f(self.hover_style());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct HoverableElement<Child> {
|
||||
hover_style: StyleRefinement,
|
||||
group: Option<SharedString>,
|
||||
cascade_slot: CascadeSlot,
|
||||
hovered: Arc<AtomicBool>,
|
||||
child: Child,
|
||||
}
|
||||
|
||||
impl<Child: Styled + Element> HoverableElement<Child> {
|
||||
fn hover_style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.hover_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoAnyElement<E::ViewState> for HoverableElement<E>
|
||||
where
|
||||
E: Element + Styled,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Element for HoverableElement<E>
|
||||
where
|
||||
E: Element + Styled,
|
||||
{
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = E::ElementState;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
self.child.element_id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut crate::ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
self.child.layout(state, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut crate::ViewContext<Self::ViewState>,
|
||||
) {
|
||||
let target_bounds = self
|
||||
.group
|
||||
.as_ref()
|
||||
.and_then(|group| group_bounds(group, cx))
|
||||
.unwrap_or(bounds);
|
||||
|
||||
let hovered = target_bounds.contains_point(cx.mouse_position());
|
||||
|
||||
let slot = self.cascade_slot;
|
||||
let style = hovered.then_some(self.hover_style.clone());
|
||||
self.child.style_cascade().set(slot, style);
|
||||
self.hovered.store(hovered, SeqCst);
|
||||
|
||||
let hovered = self.hovered.clone();
|
||||
cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
if target_bounds.contains_point(event.position) != hovered.load(SeqCst) {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.child.paint(bounds, state, element_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
struct Div<V: 'static + Send + Sync>(HoverableElement<LayoutNodeState<V>>);
|
||||
|
||||
impl<V: 'static + Send + Sync> LayoutNode<V> for Div<V> {
|
||||
fn state(&mut self) -> &mut LayoutNodeState<V> {
|
||||
&mut self.0.child
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + Send + Sync> Styled for LayoutNodeState<V> {
|
||||
fn style_cascade(&mut self) -> &mut StyleCascade {
|
||||
&mut self.style_cascade
|
||||
}
|
||||
|
||||
fn computed_style(&mut self) -> &Style {
|
||||
self.computed_style
|
||||
.get_or_insert_with(|| Style::default().refined(self.style_cascade.merged()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + Send + Sync> Styled for Div<V> {
|
||||
fn style_cascade(&mut self) -> &mut StyleCascade {
|
||||
self.0.child.style_cascade()
|
||||
}
|
||||
|
||||
fn computed_style(&mut self) -> &Style {
|
||||
self.0.child.computed_style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + Send + Sync> Hoverable for Div<V> {
|
||||
fn hover_style(&mut self) -> &mut StyleRefinement {
|
||||
self.0.hover_style()
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
use crate::{
|
||||
group_bounds, AnyElement, Bounds, DispatchPhase, Element, IdentifiedElement, Interactive,
|
||||
IntoAnyElement, MouseDownEvent, MouseEventListeners, MouseUpEvent, ParentElement, Pixels,
|
||||
SharedString, Styled, ViewContext,
|
||||
};
|
||||
use refineable::{Cascade, CascadeSlot, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
};
|
||||
|
||||
pub struct Pressable<E: Styled> {
|
||||
group: Option<SharedString>,
|
||||
cascade_slot: CascadeSlot,
|
||||
pressed_style: <E::Style as Refineable>::Refinement,
|
||||
child: E,
|
||||
}
|
||||
|
||||
pub struct PressableState<S> {
|
||||
pressed: Arc<AtomicBool>,
|
||||
child_state: S,
|
||||
}
|
||||
|
||||
impl<E: Styled> Pressable<E> {
|
||||
pub fn new(mut child: E, group: Option<SharedString>) -> Self {
|
||||
Self {
|
||||
group,
|
||||
cascade_slot: child.style_cascade().reserve(),
|
||||
pressed_style: Default::default(),
|
||||
child,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Styled for Pressable<E>
|
||||
where
|
||||
E: Styled,
|
||||
{
|
||||
type Style = E::Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
|
||||
self.child.style_cascade()
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
&mut self.pressed_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Pressable<E> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S> {
|
||||
self.child.listeners()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoAnyElement<E::ViewState> for Pressable<E>
|
||||
where
|
||||
E: Styled + IdentifiedElement,
|
||||
<E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
|
||||
<<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<E::ViewState> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Element for Pressable<E>
|
||||
where
|
||||
E: Styled + IdentifiedElement,
|
||||
<E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
|
||||
<<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
|
||||
{
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = PressableState<E::ElementState>;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
Some(IdentifiedElement::element_id(&self.child))
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) -> (crate::LayoutId, Self::ElementState) {
|
||||
if let Some(element_state) = element_state {
|
||||
let (id, child_state) = self
|
||||
.child
|
||||
.layout(state, Some(element_state.child_state), cx);
|
||||
let element_state = PressableState {
|
||||
pressed: element_state.pressed,
|
||||
child_state,
|
||||
};
|
||||
(id, element_state)
|
||||
} else {
|
||||
let (id, child_state) = self.child.layout(state, None, cx);
|
||||
let element_state = PressableState {
|
||||
pressed: Default::default(),
|
||||
child_state,
|
||||
};
|
||||
(id, element_state)
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<Self::ViewState>,
|
||||
) {
|
||||
let target_bounds = self
|
||||
.group
|
||||
.as_ref()
|
||||
.and_then(|group| group_bounds(group, cx))
|
||||
.unwrap_or(bounds);
|
||||
|
||||
let style = element_state
|
||||
.pressed
|
||||
.load(SeqCst)
|
||||
.then_some(self.pressed_style.clone());
|
||||
let slot = self.cascade_slot;
|
||||
self.style_cascade().set(slot, style);
|
||||
|
||||
let pressed = element_state.pressed.clone();
|
||||
cx.on_mouse_event(move |_, event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
if target_bounds.contains_point(event.position) {
|
||||
pressed.store(true, SeqCst);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
let pressed = element_state.pressed.clone();
|
||||
cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Capture {
|
||||
if pressed.load(SeqCst) {
|
||||
pressed.store(false, SeqCst);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.child
|
||||
.paint(bounds, state, &mut element_state.child_state, cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> ParentElement for Pressable<E>
|
||||
where
|
||||
E: ParentElement + IdentifiedElement + Styled,
|
||||
{
|
||||
type State = E::State;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
self.child.children_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IdentifiedElement for Pressable<E>
|
||||
where
|
||||
E: IdentifiedElement + Styled,
|
||||
<E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
|
||||
<<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
|
||||
{
|
||||
}
|
@ -1,85 +1,137 @@
|
||||
use crate::{
|
||||
AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Pixels, SharedString, Style, Styled,
|
||||
div, Active, AnonymousElement, AnyElement, Bounds, Click, Div, DivState, Element, ElementId,
|
||||
ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement, LayoutId,
|
||||
MouseEventListeners, Pixels, SharedString, StyleRefinement, Styled,
|
||||
};
|
||||
use refineable::Cascade;
|
||||
use std::marker::PhantomData;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct Svg<S> {
|
||||
pub struct Svg<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
|
||||
base: Div<V, K>,
|
||||
path: Option<SharedString>,
|
||||
style: Cascade<Style>,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
pub fn svg<S>() -> Svg<S> {
|
||||
pub fn svg<V>() -> Svg<V, AnonymousElement>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
Svg {
|
||||
base: div(),
|
||||
path: None,
|
||||
style: Cascade::<Style>::default(),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Svg<S> {
|
||||
impl<V, K> Svg<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||
self.path = Some(path.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoAnyElement<S> for Svg<S>
|
||||
impl<V: 'static + Send + Sync> Svg<V, AnonymousElement> {
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, IdentifiedElement> {
|
||||
Svg {
|
||||
base: self.base.id(id),
|
||||
path: self.path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> IntoAnyElement<V> for Svg<V, K>
|
||||
where
|
||||
S: 'static + Send + Sync,
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> Element for Svg<S> {
|
||||
type ViewState = S;
|
||||
type ElementState = ();
|
||||
impl<V, K> Element for Svg<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
type ViewState = V;
|
||||
type ElementState = DivState;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
self.base.id()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_: &mut S,
|
||||
_: Option<Self::ElementState>,
|
||||
cx: &mut crate::ViewContext<S>,
|
||||
view: &mut V,
|
||||
element_state: Option<Self::ElementState>,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) -> (LayoutId, Self::ElementState)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.computed_style();
|
||||
(cx.request_layout(style, []), ())
|
||||
self.base.layout(view, element_state, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::ViewState,
|
||||
_: &mut Self::ElementState,
|
||||
cx: &mut crate::ViewContext<S>,
|
||||
view: &mut Self::ViewState,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut crate::ViewContext<V>,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
|
||||
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
|
||||
cx.paint_svg(bounds, path.clone(), fill_color).log_err();
|
||||
self.base.paint(bounds, view, element_state, cx);
|
||||
let color = self
|
||||
.base
|
||||
.compute_style(bounds, element_state, cx)
|
||||
.text
|
||||
.color;
|
||||
if let Some((path, color)) = self.path.as_ref().zip(color) {
|
||||
cx.paint_svg(bounds, path.clone(), color).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> Styled for Svg<S> {
|
||||
type Style = Style;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut refineable::Cascade<Self::Style> {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
|
||||
self.style.base()
|
||||
impl<V, K> Styled for Svg<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Interactive for Svg<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<V> {
|
||||
self.base.listeners()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, K> Hover for Svg<V, K>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
K: ElementIdentity,
|
||||
{
|
||||
fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
|
||||
self.base.set_hover_style(group, style);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Click for Svg<V, IdentifiedElement> where V: 'static + Send + Sync {}
|
||||
|
||||
impl<V> Active for Svg<V, IdentifiedElement>
|
||||
where
|
||||
V: 'static + Send + Sync,
|
||||
{
|
||||
fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
|
||||
self.base.set_active_style(group, style)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
use crate::{
|
||||
AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Size, ViewContext,
|
||||
AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
|
||||
SharedString, Size, ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use util::{arc_cow::ArcCow, ResultExt};
|
||||
use util::ResultExt;
|
||||
|
||||
impl<S: 'static + Send + Sync> IntoAnyElement<S> for ArcCow<'static, str> {
|
||||
impl<S: 'static + Send + Sync> IntoAnyElement<S> for SharedString {
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
Text {
|
||||
text: self,
|
||||
@ -18,7 +20,7 @@ impl<S: 'static + Send + Sync> IntoAnyElement<S> for ArcCow<'static, str> {
|
||||
impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
|
||||
fn into_any(self) -> AnyElement<V> {
|
||||
Text {
|
||||
text: ArcCow::from(self),
|
||||
text: self.into(),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
.into_any()
|
||||
@ -30,7 +32,7 @@ impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
|
||||
impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
|
||||
fn into_any(self) -> AnyElement<S> {
|
||||
Text {
|
||||
text: ArcCow::from(self),
|
||||
text: self.into(),
|
||||
state_type: PhantomData,
|
||||
}
|
||||
.into_any()
|
||||
@ -38,7 +40,7 @@ impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
|
||||
}
|
||||
|
||||
pub struct Text<S> {
|
||||
text: ArcCow<'static, str>,
|
||||
text: SharedString,
|
||||
state_type: PhantomData<S>,
|
||||
}
|
||||
|
||||
@ -52,7 +54,7 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
|
||||
type ViewState = S;
|
||||
type ElementState = Arc<Mutex<Option<TextElementState>>>;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
@ -74,12 +76,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
|
||||
let rem_size = cx.rem_size();
|
||||
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
|
||||
let element_state = element_state.clone();
|
||||
move |_, _| {
|
||||
let Some(line_layout) = text_system
|
||||
.layout_line(
|
||||
text.as_ref(),
|
||||
move |known_dimensions, _| {
|
||||
let Some(lines) = text_system
|
||||
.layout_text(
|
||||
&text,
|
||||
font_size,
|
||||
&[(text.len(), text_style.to_run())],
|
||||
&[text_style.to_run(text.len())],
|
||||
known_dimensions.width, // Wrap if we know the width.
|
||||
)
|
||||
.log_err()
|
||||
else {
|
||||
@ -87,14 +90,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
|
||||
};
|
||||
|
||||
let size = Size {
|
||||
width: line_layout.width(),
|
||||
height: line_height,
|
||||
width: lines.iter().map(|line| line.layout.width).max().unwrap(),
|
||||
height: line_height * lines.len(),
|
||||
};
|
||||
|
||||
element_state.lock().replace(TextElementState {
|
||||
line: Arc::new(line_layout),
|
||||
line_height,
|
||||
});
|
||||
element_state
|
||||
.lock()
|
||||
.replace(TextElementState { lines, line_height });
|
||||
|
||||
size
|
||||
}
|
||||
@ -110,22 +112,20 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<S>,
|
||||
) {
|
||||
let line;
|
||||
let line_height;
|
||||
{
|
||||
let element_state = element_state.lock();
|
||||
let element_state = element_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
line = element_state.line.clone();
|
||||
line_height = element_state.line_height;
|
||||
let element_state = element_state.lock();
|
||||
let element_state = element_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
let line_height = element_state.line_height;
|
||||
let mut line_origin = bounds.origin;
|
||||
for line in &element_state.lines {
|
||||
line.paint(line_origin, line_height, cx).log_err();
|
||||
line_origin.y += line.size(line_height).height;
|
||||
}
|
||||
|
||||
line.paint(bounds, bounds, line_height, cx).log_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextElementState {
|
||||
line: Arc<Line>,
|
||||
lines: SmallVec<[Line; 1]>,
|
||||
line_height: Pixels,
|
||||
}
|
||||
|
@ -413,7 +413,7 @@ impl<T> Bounds<T>
|
||||
where
|
||||
T: Add<T, Output = T> + PartialOrd + Clone + Default + Debug,
|
||||
{
|
||||
pub fn contains_point(&self, point: Point<T>) -> bool {
|
||||
pub fn contains_point(&self, point: &Point<T>) -> bool {
|
||||
point.x >= self.origin.x
|
||||
&& point.x <= self.origin.x.clone() + self.size.width.clone()
|
||||
&& point.y >= self.origin.y
|
||||
@ -656,6 +656,14 @@ impl Mul<f32> for Pixels {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<usize> for Pixels {
|
||||
type Output = Pixels;
|
||||
|
||||
fn mul(self, other: usize) -> Pixels {
|
||||
Pixels(self.0 * other as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Pixels> for f32 {
|
||||
type Output = Pixels;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod active;
|
||||
mod app;
|
||||
mod assets;
|
||||
mod color;
|
||||
@ -6,12 +7,12 @@ mod elements;
|
||||
mod events;
|
||||
mod executor;
|
||||
mod geometry;
|
||||
mod hover;
|
||||
mod image_cache;
|
||||
mod interactive;
|
||||
mod platform;
|
||||
mod scene;
|
||||
mod style;
|
||||
mod style_helpers;
|
||||
mod styled;
|
||||
mod subscription;
|
||||
mod svg_renderer;
|
||||
@ -21,6 +22,7 @@ mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
pub use active::*;
|
||||
pub use anyhow::Result;
|
||||
pub use app::*;
|
||||
pub use assets::*;
|
||||
@ -31,6 +33,7 @@ pub use events::*;
|
||||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use gpui3_macros::*;
|
||||
pub use hover::*;
|
||||
pub use image_cache::*;
|
||||
pub use interactive::*;
|
||||
pub use platform::*;
|
||||
@ -41,7 +44,6 @@ pub use serde_json;
|
||||
pub use smallvec;
|
||||
pub use smol::Timer;
|
||||
pub use style::*;
|
||||
pub use style_helpers::*;
|
||||
pub use styled::*;
|
||||
pub use subscription::*;
|
||||
pub use svg_renderer::*;
|
||||
@ -51,6 +53,7 @@ pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
mem,
|
||||
@ -180,7 +183,7 @@ impl<T> Flatten<T> for Result<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct SharedString(ArcCow<'static, str>);
|
||||
|
||||
impl Default for SharedString {
|
||||
|
25
crates/gpui3/src/hover.rs
Normal file
25
crates/gpui3/src/hover.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::{SharedString, StyleRefinement};
|
||||
|
||||
pub trait Hover {
|
||||
fn set_hover_style(&mut self, group_name: Option<SharedString>, style: StyleRefinement);
|
||||
|
||||
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.set_hover_style(None, f(StyleRefinement::default()));
|
||||
self
|
||||
}
|
||||
|
||||
fn group_hover(
|
||||
mut self,
|
||||
group_name: impl Into<SharedString>,
|
||||
f: impl FnOnce(StyleRefinement) -> StyleRefinement,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.set_hover_style(Some(group_name.into()), f(StyleRefinement::default()));
|
||||
self
|
||||
}
|
||||
}
|
@ -1,17 +1,21 @@
|
||||
use crate::{
|
||||
Bounds, DispatchPhase, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
ScrollWheelEvent, ViewContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
Bounds, DispatchPhase, Element, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, ScrollWheelEvent, ViewContext,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait Interactive<S: 'static + Send + Sync> {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<S>;
|
||||
pub trait Interactive: Element {
|
||||
fn listeners(&mut self) -> &mut MouseEventListeners<Self::ViewState>;
|
||||
|
||||
fn on_mouse_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@ -21,7 +25,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.contains_point(event.position)
|
||||
&& bounds.contains_point(&event.position)
|
||||
{
|
||||
handler(view, event, cx)
|
||||
}
|
||||
@ -32,7 +36,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
fn on_mouse_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@ -42,7 +49,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.contains_point(event.position)
|
||||
&& bounds.contains_point(&event.position)
|
||||
{
|
||||
handler(view, event, cx)
|
||||
}
|
||||
@ -53,7 +60,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
fn on_mouse_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@ -63,7 +73,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& event.button == button
|
||||
&& !bounds.contains_point(event.position)
|
||||
&& !bounds.contains_point(&event.position)
|
||||
{
|
||||
handler(view, event, cx)
|
||||
}
|
||||
@ -74,7 +84,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
fn on_mouse_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@ -84,7 +97,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& event.button == button
|
||||
&& !bounds.contains_point(event.position)
|
||||
&& !bounds.contains_point(&event.position)
|
||||
{
|
||||
handler(view, event, cx);
|
||||
}
|
||||
@ -94,7 +107,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
|
||||
fn on_mouse_move(
|
||||
mut self,
|
||||
handler: impl Fn(&mut S, &MouseMoveEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
handler: impl Fn(&mut Self::ViewState, &MouseMoveEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@ -102,7 +118,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
self.listeners()
|
||||
.mouse_move
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
}));
|
||||
@ -111,7 +127,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
|
||||
fn on_scroll_wheel(
|
||||
mut self,
|
||||
handler: impl Fn(&mut S, &ScrollWheelEvent, &mut ViewContext<S>) + Send + Sync + 'static,
|
||||
handler: impl Fn(&mut Self::ViewState, &ScrollWheelEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
@ -119,7 +138,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
self.listeners()
|
||||
.scroll_wheel
|
||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
handler(view, event, cx);
|
||||
}
|
||||
}));
|
||||
@ -127,6 +146,24 @@ pub trait Interactive<S: 'static + Send + Sync> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Click: Interactive {
|
||||
fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext<Self::ViewState>)
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.listeners()
|
||||
.mouse_click
|
||||
.push(Arc::new(move |view, event, cx| handler(view, event, cx)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type MouseDownHandler<V> = Arc<
|
||||
dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
+ Send
|
||||
@ -139,6 +176,8 @@ type MouseUpHandler<V> = Arc<
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
type MouseClickHandler<V> =
|
||||
Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
|
||||
|
||||
type MouseMoveHandler<V> = Arc<
|
||||
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||
@ -154,45 +193,26 @@ type ScrollWheelHandler<V> = Arc<
|
||||
>;
|
||||
|
||||
pub struct MouseEventListeners<V: 'static> {
|
||||
mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
|
||||
mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
|
||||
mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
|
||||
scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
|
||||
}
|
||||
|
||||
impl<S: Send + Sync + 'static> MouseEventListeners<S> {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<S>) {
|
||||
for handler in self.mouse_down.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &MouseDownEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_up.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
for handler in self.mouse_move.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &MouseMoveEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
|
||||
for handler in self.scroll_wheel.iter().cloned() {
|
||||
cx.on_mouse_event(move |view, event: &ScrollWheelEvent, phase, cx| {
|
||||
handler(view, event, &bounds, phase, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
pub mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
|
||||
pub mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
|
||||
pub mouse_click: SmallVec<[MouseClickHandler<V>; 2]>,
|
||||
pub mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
|
||||
pub scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
|
||||
}
|
||||
|
||||
impl<V> Default for MouseEventListeners<V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mouse_down: Default::default(),
|
||||
mouse_up: Default::default(),
|
||||
mouse_move: Default::default(),
|
||||
scroll_wheel: Default::default(),
|
||||
mouse_down: SmallVec::new(),
|
||||
mouse_up: SmallVec::new(),
|
||||
mouse_click: SmallVec::new(),
|
||||
mouse_move: SmallVec::new(),
|
||||
scroll_wheel: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MouseClickEvent {
|
||||
pub down: MouseDownEvent,
|
||||
pub up: MouseUpEvent,
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ mod mac;
|
||||
mod test;
|
||||
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics,
|
||||
GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams,
|
||||
Result, Scene, ShapedLine, SharedString, Size,
|
||||
AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun,
|
||||
GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams,
|
||||
RenderSvgParams, Result, Scene, SharedString, Size,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use async_task::Runnable;
|
||||
@ -171,7 +171,7 @@ pub trait PlatformTextSystem: Send + Sync {
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
|
||||
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
|
||||
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
|
||||
FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph,
|
||||
ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
|
||||
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
|
||||
FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
|
||||
RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use cocoa::appkit::{CGFloat, CGPoint};
|
||||
@ -149,12 +149,7 @@ impl PlatformTextSystem for MacTextSystem {
|
||||
self.0.read().rasterize_glyph(glyph_id)
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
font_runs: &[(usize, FontId)],
|
||||
) -> ShapedLine {
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
self.0.write().layout_line(text, font_size, font_runs)
|
||||
}
|
||||
|
||||
@ -337,12 +332,7 @@ impl MacTextSystemState {
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&mut self,
|
||||
text: &str,
|
||||
font_size: Pixels,
|
||||
font_runs: &[(usize, FontId)],
|
||||
) -> ShapedLine {
|
||||
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
{
|
||||
@ -350,8 +340,8 @@ impl MacTextSystemState {
|
||||
let utf16_line_len = string.char_len() as usize;
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
for (run_len, font_id) in font_runs {
|
||||
let utf8_end = ix_converter.utf8_ix + run_len;
|
||||
for run in font_runs {
|
||||
let utf8_end = ix_converter.utf8_ix + run.len;
|
||||
let utf16_start = ix_converter.utf16_ix;
|
||||
|
||||
if utf16_start >= utf16_line_len {
|
||||
@ -364,7 +354,7 @@ impl MacTextSystemState {
|
||||
let cf_range =
|
||||
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
|
||||
|
||||
let font: &FontKitFont = &self.fonts[font_id.0];
|
||||
let font: &FontKitFont = &self.fonts[run.font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
@ -394,7 +384,7 @@ impl MacTextSystemState {
|
||||
let font_id = self.id_for_native_font(font);
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut glyphs = Vec::new();
|
||||
let mut glyphs = SmallVec::new();
|
||||
for ((glyph_id, position), glyph_utf16_ix) in run
|
||||
.glyphs()
|
||||
.iter()
|
||||
@ -415,13 +405,12 @@ impl MacTextSystemState {
|
||||
}
|
||||
|
||||
let typographic_bounds = line.get_typographic_bounds();
|
||||
ShapedLine {
|
||||
LineLayout {
|
||||
width: typographic_bounds.width.into(),
|
||||
ascent: typographic_bounds.ascent.into(),
|
||||
descent: typographic_bounds.descent.into(),
|
||||
runs,
|
||||
font_size,
|
||||
len: text.len(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
|
||||
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
|
||||
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString,
|
||||
Size, SizeRefinement, ViewContext, WindowContext,
|
||||
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||
Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
|
||||
FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result,
|
||||
SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
|
||||
};
|
||||
use refineable::{Cascade, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
@ -83,7 +83,7 @@ pub struct Style {
|
||||
pub flex_shrink: f32,
|
||||
|
||||
/// The fill color of this element
|
||||
pub fill: Option<Fill>,
|
||||
pub background: Option<Fill>,
|
||||
|
||||
/// The border color of this element
|
||||
pub border_color: Option<Hsla>,
|
||||
@ -101,6 +101,12 @@ pub struct Style {
|
||||
pub z_index: Option<u32>,
|
||||
}
|
||||
|
||||
impl Styled for StyleRefinement {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoxShadow {
|
||||
pub color: Hsla,
|
||||
@ -125,8 +131,8 @@ pub struct TextStyle {
|
||||
impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
TextStyle {
|
||||
color: Hsla::default(),
|
||||
font_family: SharedString::default(),
|
||||
color: black(),
|
||||
font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system")
|
||||
font_features: FontFeatures::default(),
|
||||
font_size: rems(1.),
|
||||
line_height: phi(),
|
||||
@ -161,8 +167,9 @@ impl TextStyle {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn to_run(&self) -> RunStyle {
|
||||
RunStyle {
|
||||
pub fn to_run(&self, len: usize) -> TextRun {
|
||||
TextRun {
|
||||
len,
|
||||
font: Font {
|
||||
family: self.font_family.clone(),
|
||||
features: Default::default(),
|
||||
@ -256,7 +263,7 @@ impl Style {
|
||||
);
|
||||
});
|
||||
|
||||
let background_color = self.fill.as_ref().and_then(Fill::color);
|
||||
let background_color = self.background.as_ref().and_then(Fill::color);
|
||||
if background_color.is_some() || self.is_border_visible() {
|
||||
cx.stack(1, |cx| {
|
||||
cx.paint_quad(
|
||||
@ -307,7 +314,7 @@ impl Default for Style {
|
||||
flex_grow: 0.0,
|
||||
flex_shrink: 1.0,
|
||||
flex_basis: Length::Auto,
|
||||
fill: None,
|
||||
background: None,
|
||||
border_color: None,
|
||||
corner_radii: Corners::default(),
|
||||
box_shadow: Default::default(),
|
||||
|
@ -1,387 +0,0 @@
|
||||
use crate::{
|
||||
self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
|
||||
FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
|
||||
Styled, TextStyleRefinement,
|
||||
};
|
||||
use smallvec::smallvec;
|
||||
|
||||
pub trait StyleHelpers: Sized + Styled<Style = Style> {
|
||||
gpui3_macros::style_helpers!();
|
||||
|
||||
/// Sets the size of the element to the full width and height.
|
||||
fn full(mut self) -> Self {
|
||||
self.declared_style().size.width = Some(relative(1.).into());
|
||||
self.declared_style().size.height = Some(relative(1.).into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position of the element to `relative`.
|
||||
/// [Docs](https://tailwindcss.com/docs/position)
|
||||
fn relative(mut self) -> Self {
|
||||
self.declared_style().position = Some(Position::Relative);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position of the element to `absolute`.
|
||||
/// [Docs](https://tailwindcss.com/docs/position)
|
||||
fn absolute(mut self) -> Self {
|
||||
self.declared_style().position = Some(Position::Absolute);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the display type of the element to `block`.
|
||||
/// [Docs](https://tailwindcss.com/docs/display)
|
||||
fn block(mut self) -> Self {
|
||||
self.declared_style().display = Some(Display::Block);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the display type of the element to `flex`.
|
||||
/// [Docs](https://tailwindcss.com/docs/display)
|
||||
fn flex(mut self) -> Self {
|
||||
self.declared_style().display = Some(Display::Flex);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the flex directino of the element to `column`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
|
||||
fn flex_col(mut self) -> Self {
|
||||
self.declared_style().flex_direction = Some(FlexDirection::Column);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the flex directino of the element to `row`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#row)
|
||||
fn flex_row(mut self) -> Self {
|
||||
self.declared_style().flex_direction = Some(FlexDirection::Row);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#flex-1)
|
||||
fn flex_1(mut self) -> Self {
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(relative(0.).into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow and shrink, taking into account its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#auto)
|
||||
fn flex_auto(mut self) -> Self {
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to shrink but not grow, taking into account its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#initial)
|
||||
fn flex_initial(mut self) -> Self {
|
||||
self.declared_style().flex_grow = Some(0.);
|
||||
self.declared_style().flex_shrink = Some(1.);
|
||||
self.declared_style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to prevent a flex item from growing or shrinking.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#none)
|
||||
fn flex_none(mut self) -> Self {
|
||||
self.declared_style().flex_grow = Some(0.);
|
||||
self.declared_style().flex_shrink = Some(0.);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow to fill any available space.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-grow)
|
||||
fn grow(mut self) -> Self {
|
||||
self.declared_style().flex_grow = Some(1.);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_start(mut self) -> Self {
|
||||
self.declared_style().align_items = Some(AlignItems::FlexStart);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_end(mut self) -> Self {
|
||||
self.declared_style().align_items = Some(AlignItems::FlexEnd);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_center(mut self) -> Self {
|
||||
self.declared_style().align_items = Some(AlignItems::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_between(mut self) -> Self {
|
||||
self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_center(mut self) -> Self {
|
||||
self.declared_style().justify_content = Some(JustifyContent::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_start(mut self) -> Self {
|
||||
self.declared_style().justify_content = Some(JustifyContent::Start);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_end(mut self) -> Self {
|
||||
self.declared_style().justify_content = Some(JustifyContent::End);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_around(mut self) -> Self {
|
||||
self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
|
||||
self
|
||||
}
|
||||
|
||||
fn fill<F>(mut self, fill: F) -> Self
|
||||
where
|
||||
F: Into<Fill>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().fill = Some(fill.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn border_color<C>(mut self, border_color: C) -> Self
|
||||
where
|
||||
C: Into<Hsla>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.declared_style().border_color = Some(border_color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(3.),
|
||||
spread_radius: px(0.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(-1.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_none(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_sm(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(smallvec![BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.05),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_md(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0.5, 0., 0., 0.1),
|
||||
offset: point(px(0.), px(4.)),
|
||||
blur_radius: px(6.),
|
||||
spread_radius: px(-1.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(2.)),
|
||||
blur_radius: px(4.),
|
||||
spread_radius: px(-2.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_lg(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(10.)),
|
||||
blur_radius: px(15.),
|
||||
spread_radius: px(-3.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(4.)),
|
||||
blur_radius: px(6.),
|
||||
spread_radius: px(-4.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_xl(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(20.)),
|
||||
blur_radius: px(25.),
|
||||
spread_radius: px(-5.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(10.),
|
||||
spread_radius: px(-6.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_2xl(mut self) -> Self {
|
||||
self.declared_style().box_shadow = Some(smallvec![BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.25),
|
||||
offset: point(px(0.), px(25.)),
|
||||
blur_radius: px(50.),
|
||||
spread_radius: px(-12.),
|
||||
}]);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
|
||||
let style: &mut StyleRefinement = self.declared_style();
|
||||
&mut style.text
|
||||
}
|
||||
|
||||
fn text_color(mut self, color: impl Into<Hsla>) -> Self {
|
||||
self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xs(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.75));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_sm(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.875));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_base(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.0));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_lg(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.125));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xl(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.25));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_2xl(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.5));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_3xl(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.875));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_none(mut self) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.underline = None;
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_solid(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.wavy = false;
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_wavy(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.wavy = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_0(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(0.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_1(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(1.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_2(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(2.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_4(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(4.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_8(mut self) -> Self {
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(8.);
|
||||
self
|
||||
}
|
||||
|
||||
fn font(mut self, family_name: impl Into<SharedString>) -> Self {
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_family = Some(family_name.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styled<Style = Style>> StyleHelpers for E {}
|
@ -1,48 +1,522 @@
|
||||
use crate::{Cascade, Hoverable, Pressable, Refineable, SharedString};
|
||||
use crate::{
|
||||
self as gpui3, hsla, point, px, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla,
|
||||
JustifyContent, Length, Position, SharedString, StyleRefinement,
|
||||
};
|
||||
use crate::{BoxShadow, TextStyleRefinement};
|
||||
use smallvec::smallvec;
|
||||
|
||||
pub trait Styled {
|
||||
type Style: 'static + Refineable + Send + Sync + Default;
|
||||
fn style(&mut self) -> &mut StyleRefinement;
|
||||
|
||||
fn style_cascade(&mut self) -> &mut Cascade<Self::Style>;
|
||||
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
|
||||
gpui3_macros::style_helpers!();
|
||||
|
||||
fn computed_style(&mut self) -> Self::Style {
|
||||
Self::Style::default().refined(self.style_cascade().merged())
|
||||
/// Sets the size of the element to the full width and height.
|
||||
fn full(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().size.width = Some(relative(1.).into());
|
||||
self.style().size.height = Some(relative(1.).into());
|
||||
self
|
||||
}
|
||||
|
||||
fn hover(self) -> Hoverable<Self>
|
||||
/// Sets the position of the element to `relative`.
|
||||
/// [Docs](https://tailwindcss.com/docs/position)
|
||||
fn relative(mut self) -> Self
|
||||
where
|
||||
Self: 'static + Sized + Send + Sync,
|
||||
Self::Style: 'static + Refineable + Default + Send + Sync,
|
||||
<Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
|
||||
Self: Sized,
|
||||
{
|
||||
Hoverable::new(self, None)
|
||||
self.style().position = Some(Position::Relative);
|
||||
self
|
||||
}
|
||||
|
||||
fn group_hover(self, group_name: impl Into<SharedString>) -> Hoverable<Self>
|
||||
/// Sets the position of the element to `absolute`.
|
||||
/// [Docs](https://tailwindcss.com/docs/position)
|
||||
fn absolute(mut self) -> Self
|
||||
where
|
||||
Self: 'static + Sized + Send + Sync,
|
||||
Self::Style: 'static + Refineable + Default + Send + Sync,
|
||||
<Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
|
||||
Self: Sized,
|
||||
{
|
||||
Hoverable::new(self, Some(group_name.into()))
|
||||
self.style().position = Some(Position::Absolute);
|
||||
self
|
||||
}
|
||||
|
||||
fn active(self) -> Pressable<Self>
|
||||
/// Sets the display type of the element to `block`.
|
||||
/// [Docs](https://tailwindcss.com/docs/display)
|
||||
fn block(mut self) -> Self
|
||||
where
|
||||
Self: 'static + Sized + Send + Sync,
|
||||
Self::Style: 'static + Refineable + Default + Send + Sync,
|
||||
<Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
|
||||
Self: Sized,
|
||||
{
|
||||
Pressable::new(self, None)
|
||||
self.style().display = Some(Display::Block);
|
||||
self
|
||||
}
|
||||
|
||||
fn group_active(self, group_name: impl Into<SharedString>) -> Pressable<Self>
|
||||
/// Sets the display type of the element to `flex`.
|
||||
/// [Docs](https://tailwindcss.com/docs/display)
|
||||
fn flex(mut self) -> Self
|
||||
where
|
||||
Self: 'static + Sized + Send + Sync,
|
||||
Self::Style: 'static + Refineable + Default + Send + Sync,
|
||||
<Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
|
||||
Self: Sized,
|
||||
{
|
||||
Pressable::new(self, Some(group_name.into()))
|
||||
self.style().display = Some(Display::Flex);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the flex directino of the element to `column`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#column)
|
||||
fn flex_col(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_direction = Some(FlexDirection::Column);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the flex directino of the element to `row`.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-direction#row)
|
||||
fn flex_row(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_direction = Some(FlexDirection::Row);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#flex-1)
|
||||
fn flex_1(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_grow = Some(1.);
|
||||
self.style().flex_shrink = Some(1.);
|
||||
self.style().flex_basis = Some(relative(0.).into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow and shrink, taking into account its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#auto)
|
||||
fn flex_auto(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_grow = Some(1.);
|
||||
self.style().flex_shrink = Some(1.);
|
||||
self.style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to shrink but not grow, taking into account its initial size.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#initial)
|
||||
fn flex_initial(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_grow = Some(0.);
|
||||
self.style().flex_shrink = Some(1.);
|
||||
self.style().flex_basis = Some(Length::Auto);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to prevent a flex item from growing or shrinking.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex#none)
|
||||
fn flex_none(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_grow = Some(0.);
|
||||
self.style().flex_shrink = Some(0.);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the element to allow a flex item to grow to fill any available space.
|
||||
/// [Docs](https://tailwindcss.com/docs/flex-grow)
|
||||
fn grow(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().flex_grow = Some(1.);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_start(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().align_items = Some(AlignItems::FlexStart);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_end(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().align_items = Some(AlignItems::FlexEnd);
|
||||
self
|
||||
}
|
||||
|
||||
fn items_center(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().align_items = Some(AlignItems::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_between(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().justify_content = Some(JustifyContent::SpaceBetween);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_center(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().justify_content = Some(JustifyContent::Center);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_start(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().justify_content = Some(JustifyContent::Start);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_end(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().justify_content = Some(JustifyContent::End);
|
||||
self
|
||||
}
|
||||
|
||||
fn justify_around(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().justify_content = Some(JustifyContent::SpaceAround);
|
||||
self
|
||||
}
|
||||
|
||||
fn bg<F>(mut self, fill: F) -> Self
|
||||
where
|
||||
F: Into<Fill>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().background = Some(fill.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn border_color<C>(mut self, border_color: C) -> Self
|
||||
where
|
||||
C: Into<Hsla>,
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().border_color = Some(border_color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(3.),
|
||||
spread_radius: px(0.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(-1.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_none(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(Default::default());
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_sm(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(smallvec::smallvec![BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.05),
|
||||
offset: point(px(0.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_md(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0.5, 0., 0., 0.1),
|
||||
offset: point(px(0.), px(4.)),
|
||||
blur_radius: px(6.),
|
||||
spread_radius: px(-1.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(2.)),
|
||||
blur_radius: px(4.),
|
||||
spread_radius: px(-2.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_lg(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(10.)),
|
||||
blur_radius: px(15.),
|
||||
spread_radius: px(-3.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(4.)),
|
||||
blur_radius: px(6.),
|
||||
spread_radius: px(-4.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(20.)),
|
||||
blur_radius: px(25.),
|
||||
spread_radius: px(-5.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(10.),
|
||||
spread_radius: px(-6.),
|
||||
}
|
||||
]);
|
||||
self
|
||||
}
|
||||
|
||||
fn shadow_2xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.style().box_shadow = Some(smallvec![BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.25),
|
||||
offset: point(px(0.), px(25.)),
|
||||
blur_radius: px(50.),
|
||||
spread_radius: px(-12.),
|
||||
}]);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
|
||||
let style: &mut StyleRefinement = self.style();
|
||||
&mut style.text
|
||||
}
|
||||
|
||||
fn text_color(mut self, color: impl Into<Hsla>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xs(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.75));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_sm(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.875));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_base(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.0));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_lg(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.125));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.25));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_2xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.5));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_3xl(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.875));
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_none(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.underline = None;
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_solid(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.wavy = false;
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_wavy(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.wavy = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_0(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(0.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_1(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(1.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_2(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(2.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_4(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(4.);
|
||||
self
|
||||
}
|
||||
|
||||
fn text_decoration_8(mut self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let style = self.text_style().get_or_insert_with(Default::default);
|
||||
let underline = style.underline.get_or_insert_with(Default::default);
|
||||
underline.thickness = px(8.);
|
||||
self
|
||||
}
|
||||
|
||||
fn font(mut self, family_name: impl Into<SharedString>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_family = Some(family_name.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl TaffyLayoutEngine {
|
||||
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
style: &Style,
|
||||
rem_size: Pixels,
|
||||
children: &[LayoutId],
|
||||
) -> LayoutId {
|
||||
|
@ -1,13 +1,14 @@
|
||||
mod font_features;
|
||||
mod line;
|
||||
mod line_layout;
|
||||
mod line_wrapper;
|
||||
mod text_layout_cache;
|
||||
|
||||
use anyhow::anyhow;
|
||||
pub use font_features::*;
|
||||
pub use line::*;
|
||||
pub use line_layout::*;
|
||||
use line_wrapper::*;
|
||||
pub use text_layout_cache::*;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
|
||||
@ -17,6 +18,7 @@ use collections::HashMap;
|
||||
use core::fmt;
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use std::{
|
||||
cmp,
|
||||
fmt::{Debug, Display, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
ops::{Deref, DerefMut},
|
||||
@ -33,18 +35,18 @@ pub struct FontFamilyId(pub usize);
|
||||
pub const SUBPIXEL_VARIANTS: u8 = 4;
|
||||
|
||||
pub struct TextSystem {
|
||||
text_layout_cache: Arc<TextLayoutCache>,
|
||||
line_layout_cache: Arc<LineLayoutCache>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
||||
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
|
||||
wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
||||
font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
|
||||
font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
|
||||
}
|
||||
|
||||
impl TextSystem {
|
||||
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
TextSystem {
|
||||
text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
|
||||
line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
|
||||
platform_text_system,
|
||||
font_metrics: RwLock::new(HashMap::default()),
|
||||
font_ids_by_font: RwLock::new(HashMap::default()),
|
||||
@ -143,38 +145,82 @@ impl TextSystem {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_line(
|
||||
pub fn layout_text(
|
||||
&self,
|
||||
text: &str,
|
||||
text: &SharedString,
|
||||
font_size: Pixels,
|
||||
runs: &[(usize, RunStyle)],
|
||||
) -> Result<Line> {
|
||||
runs: &[TextRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
) -> Result<SmallVec<[Line; 1]>> {
|
||||
let mut runs = runs.iter().cloned().peekable();
|
||||
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
||||
|
||||
let mut last_font: Option<&Font> = None;
|
||||
for (len, style) in runs {
|
||||
if let Some(last_font) = last_font.as_ref() {
|
||||
if **last_font == style.font {
|
||||
font_runs.last_mut().unwrap().0 += len;
|
||||
continue;
|
||||
let mut lines = SmallVec::new();
|
||||
let mut line_start = 0;
|
||||
for line_text in text.split('\n') {
|
||||
let line_text = SharedString::from(line_text.to_string());
|
||||
let line_end = line_start + line_text.len();
|
||||
|
||||
let mut last_font: Option<Font> = None;
|
||||
let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
|
||||
let mut run_start = line_start;
|
||||
while run_start < line_end {
|
||||
let Some(run) = runs.peek_mut() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
|
||||
|
||||
if last_font == Some(run.font.clone()) {
|
||||
font_runs.last_mut().unwrap().len += run_len_within_line;
|
||||
} else {
|
||||
last_font = Some(run.font.clone());
|
||||
font_runs.push(FontRun {
|
||||
len: run_len_within_line,
|
||||
font_id: self.platform_text_system.font_id(&run.font)?,
|
||||
});
|
||||
}
|
||||
|
||||
if decoration_runs.last().map_or(false, |last_run| {
|
||||
last_run.color == run.color && last_run.underline == run.underline
|
||||
}) {
|
||||
decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
|
||||
} else {
|
||||
decoration_runs.push(DecorationRun {
|
||||
len: run_len_within_line as u32,
|
||||
color: run.color,
|
||||
underline: run.underline.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if run_len_within_line == run.len {
|
||||
runs.next();
|
||||
} else {
|
||||
// Preserve the remainder of the run for the next line
|
||||
run.len -= run_len_within_line;
|
||||
}
|
||||
run_start += run_len_within_line;
|
||||
}
|
||||
last_font = Some(&style.font);
|
||||
font_runs.push((*len, self.font_id(&style.font)?));
|
||||
|
||||
let layout = self
|
||||
.line_layout_cache
|
||||
.layout_line(&line_text, font_size, &font_runs, wrap_width);
|
||||
lines.push(Line {
|
||||
layout,
|
||||
decorations: decoration_runs,
|
||||
});
|
||||
|
||||
line_start = line_end + 1; // Skip `\n` character.
|
||||
font_runs.clear();
|
||||
}
|
||||
|
||||
let layout = self
|
||||
.text_layout_cache
|
||||
.layout_line(text, font_size, &font_runs);
|
||||
|
||||
font_runs.clear();
|
||||
self.font_runs_pool.lock().push(font_runs);
|
||||
|
||||
Ok(Line::new(layout.clone(), runs))
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
pub fn end_frame(&self) {
|
||||
self.text_layout_cache.end_frame()
|
||||
self.line_layout_cache.end_frame()
|
||||
}
|
||||
|
||||
pub fn line_wrapper(
|
||||
@ -317,7 +363,8 @@ impl Display for FontStyle {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RunStyle {
|
||||
pub struct TextRun {
|
||||
pub len: usize,
|
||||
pub font: Font,
|
||||
pub color: Hsla,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
@ -345,30 +392,6 @@ impl From<u32> for GlyphId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ShapedLine {
|
||||
pub font_size: Pixels,
|
||||
pub width: Pixels,
|
||||
pub ascent: Pixels,
|
||||
pub descent: Pixels,
|
||||
pub runs: Vec<ShapedRun>,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShapedRun {
|
||||
pub font_id: FontId,
|
||||
pub glyphs: Vec<ShapedGlyph>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ShapedGlyph {
|
||||
pub id: GlyphId,
|
||||
pub position: Point<Pixels>,
|
||||
pub index: usize,
|
||||
pub is_emoji: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RenderGlyphParams {
|
||||
pub(crate) font_id: FontId,
|
||||
|
@ -1,144 +1,94 @@
|
||||
use crate::{
|
||||
black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
|
||||
ShapedRun, UnderlineStyle, WindowContext,
|
||||
black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
|
||||
UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Line {
|
||||
layout: Arc<ShapedLine>,
|
||||
style_runs: SmallVec<[StyleRun; 32]>,
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DecorationRun {
|
||||
pub len: u32,
|
||||
pub color: Hsla,
|
||||
pub underline: Option<UnderlineStyle>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StyleRun {
|
||||
len: u32,
|
||||
color: Hsla,
|
||||
underline: UnderlineStyle,
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Line {
|
||||
pub(crate) layout: Arc<WrappedLineLayout>,
|
||||
pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self {
|
||||
let mut style_runs = SmallVec::new();
|
||||
for (len, style) in runs {
|
||||
style_runs.push(StyleRun {
|
||||
len: *len as u32,
|
||||
color: style.color,
|
||||
underline: style.underline.clone().unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
Self { layout, style_runs }
|
||||
}
|
||||
|
||||
pub fn runs(&self) -> &[ShapedRun] {
|
||||
&self.layout.runs
|
||||
}
|
||||
|
||||
pub fn width(&self) -> Pixels {
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> Pixels {
|
||||
self.layout.font_size
|
||||
}
|
||||
|
||||
pub fn x_for_index(&self, index: usize) -> Pixels {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return glyph.position.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||
for run in &self.layout.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return Some(run.font_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.layout.len
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.layout.len == 0
|
||||
}
|
||||
|
||||
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
|
||||
if x >= self.layout.width {
|
||||
None
|
||||
} else {
|
||||
for run in self.layout.runs.iter().rev() {
|
||||
for glyph in run.glyphs.iter().rev() {
|
||||
if glyph.position.x <= x {
|
||||
return Some(glyph.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(0)
|
||||
}
|
||||
pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
|
||||
size(
|
||||
self.layout.width,
|
||||
line_height * (self.layout.wrap_boundaries.len() + 1),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn paint(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
visible_bounds: Bounds<Pixels>, // todo!("use clipping")
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
let origin = bounds.origin;
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
||||
let padding_top =
|
||||
(line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
|
||||
|
||||
let mut style_runs = self.style_runs.iter();
|
||||
let mut style_runs = self.decorations.iter();
|
||||
let mut wraps = self.layout.wrap_boundaries.iter().peekable();
|
||||
let mut run_end = 0;
|
||||
let mut color = black();
|
||||
let mut underline = None;
|
||||
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
let text_system = cx.text_system().clone();
|
||||
|
||||
for run in &self.layout.runs {
|
||||
let max_glyph_width = text_system
|
||||
.bounding_box(run.font_id, self.layout.font_size)?
|
||||
.size
|
||||
.width;
|
||||
let mut glyph_origin = origin;
|
||||
let mut prev_glyph_position = Point::default();
|
||||
for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
|
||||
let max_glyph_size = text_system
|
||||
.bounding_box(run.font_id, self.layout.layout.font_size)?
|
||||
.size;
|
||||
|
||||
for glyph in &run.glyphs {
|
||||
let glyph_origin = origin + baseline_offset + glyph.position;
|
||||
if glyph_origin.x > visible_bounds.upper_right().x {
|
||||
break;
|
||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
||||
|
||||
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
|
||||
wraps.next();
|
||||
if let Some((underline_origin, underline_style)) = current_underline.take() {
|
||||
cx.paint_underline(
|
||||
underline_origin,
|
||||
glyph_origin.x - underline_origin.x,
|
||||
&underline_style,
|
||||
)?;
|
||||
}
|
||||
|
||||
glyph_origin.x = origin.x;
|
||||
glyph_origin.y += line_height;
|
||||
}
|
||||
prev_glyph_position = glyph.position;
|
||||
let glyph_origin = glyph_origin + baseline_offset;
|
||||
|
||||
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
if glyph.index >= run_end {
|
||||
if let Some(style_run) = style_runs.next() {
|
||||
if let Some((_, underline_style)) = &mut underline {
|
||||
if style_run.underline != *underline_style {
|
||||
finished_underline = underline.take();
|
||||
if let Some((_, underline_style)) = &mut current_underline {
|
||||
if style_run.underline.as_ref() != Some(underline_style) {
|
||||
finished_underline = current_underline.take();
|
||||
}
|
||||
}
|
||||
if style_run.underline.thickness > px(0.) {
|
||||
underline.get_or_insert((
|
||||
if let Some(run_underline) = style_run.underline.as_ref() {
|
||||
current_underline.get_or_insert((
|
||||
point(
|
||||
glyph_origin.x,
|
||||
origin.y + baseline_offset.y + (self.layout.descent * 0.618),
|
||||
origin.y
|
||||
+ baseline_offset.y
|
||||
+ (self.layout.layout.descent * 0.618),
|
||||
),
|
||||
UnderlineStyle {
|
||||
color: Some(
|
||||
style_run.underline.color.unwrap_or(style_run.color),
|
||||
),
|
||||
thickness: style_run.underline.thickness,
|
||||
wavy: style_run.underline.wavy,
|
||||
color: Some(run_underline.color.unwrap_or(style_run.color)),
|
||||
thickness: run_underline.thickness,
|
||||
wavy: run_underline.wavy,
|
||||
},
|
||||
));
|
||||
}
|
||||
@ -146,15 +96,11 @@ impl Line {
|
||||
run_end += style_run.len as usize;
|
||||
color = style_run.color;
|
||||
} else {
|
||||
run_end = self.layout.len;
|
||||
finished_underline = underline.take();
|
||||
run_end = self.layout.text.len();
|
||||
finished_underline = current_underline.take();
|
||||
}
|
||||
}
|
||||
|
||||
if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||
cx.paint_underline(
|
||||
underline_origin,
|
||||
@ -163,22 +109,35 @@ impl Line {
|
||||
)?;
|
||||
}
|
||||
|
||||
if glyph.is_emoji {
|
||||
cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
|
||||
} else {
|
||||
cx.paint_glyph(
|
||||
glyph_origin,
|
||||
run.font_id,
|
||||
glyph.id,
|
||||
self.layout.font_size,
|
||||
color,
|
||||
)?;
|
||||
let max_glyph_bounds = Bounds {
|
||||
origin: glyph_origin,
|
||||
size: max_glyph_size,
|
||||
};
|
||||
|
||||
let content_mask = cx.content_mask();
|
||||
if max_glyph_bounds.intersects(&content_mask.bounds) {
|
||||
if glyph.is_emoji {
|
||||
cx.paint_emoji(
|
||||
glyph_origin,
|
||||
run.font_id,
|
||||
glyph.id,
|
||||
self.layout.layout.font_size,
|
||||
)?;
|
||||
} else {
|
||||
cx.paint_glyph(
|
||||
glyph_origin,
|
||||
run.font_id,
|
||||
glyph.id,
|
||||
self.layout.layout.font_size,
|
||||
color,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((underline_start, underline_style)) = underline.take() {
|
||||
let line_end_x = origin.x + self.layout.width;
|
||||
if let Some((underline_start, underline_style)) = current_underline.take() {
|
||||
let line_end_x = origin.x + self.layout.layout.width;
|
||||
cx.paint_underline(
|
||||
underline_start,
|
||||
line_end_x - underline_start.x,
|
||||
@ -188,125 +147,4 @@ impl Line {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn paint_wrapped(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
_visible_bounds: Bounds<Pixels>, // todo!("use clipping")
|
||||
line_height: Pixels,
|
||||
boundaries: &[ShapedBoundary],
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
||||
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
||||
|
||||
let mut boundaries = boundaries.into_iter().peekable();
|
||||
let mut color_runs = self.style_runs.iter();
|
||||
let mut style_run_end = 0;
|
||||
let mut _color = black(); // todo!
|
||||
let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
|
||||
let mut glyph_origin = origin;
|
||||
let mut prev_position = px(0.);
|
||||
for (run_ix, run) in self.layout.runs.iter().enumerate() {
|
||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||
glyph_origin.x += glyph.position.x - prev_position;
|
||||
|
||||
if boundaries
|
||||
.peek()
|
||||
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
|
||||
{
|
||||
boundaries.next();
|
||||
if let Some((underline_origin, underline_style)) = underline.take() {
|
||||
cx.paint_underline(
|
||||
underline_origin,
|
||||
glyph_origin.x - underline_origin.x,
|
||||
&underline_style,
|
||||
)?;
|
||||
}
|
||||
|
||||
glyph_origin = point(origin.x, glyph_origin.y + line_height);
|
||||
}
|
||||
prev_position = glyph.position.x;
|
||||
|
||||
let mut finished_underline = None;
|
||||
if glyph.index >= style_run_end {
|
||||
if let Some(style_run) = color_runs.next() {
|
||||
style_run_end += style_run.len as usize;
|
||||
_color = style_run.color;
|
||||
if let Some((_, underline_style)) = &mut underline {
|
||||
if style_run.underline != *underline_style {
|
||||
finished_underline = underline.take();
|
||||
}
|
||||
}
|
||||
if style_run.underline.thickness > px(0.) {
|
||||
underline.get_or_insert((
|
||||
glyph_origin
|
||||
+ point(
|
||||
px(0.),
|
||||
baseline_offset.y + (self.layout.descent * 0.618),
|
||||
),
|
||||
UnderlineStyle {
|
||||
color: Some(
|
||||
style_run.underline.color.unwrap_or(style_run.color),
|
||||
),
|
||||
thickness: style_run.underline.thickness,
|
||||
wavy: style_run.underline.wavy,
|
||||
},
|
||||
));
|
||||
}
|
||||
} else {
|
||||
style_run_end = self.layout.len;
|
||||
_color = black();
|
||||
finished_underline = underline.take();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||
cx.paint_underline(
|
||||
underline_origin,
|
||||
glyph_origin.x - underline_origin.x,
|
||||
&underline_style,
|
||||
)?;
|
||||
}
|
||||
|
||||
let text_system = cx.text_system();
|
||||
let _glyph_bounds = Bounds {
|
||||
origin: glyph_origin,
|
||||
size: text_system
|
||||
.bounding_box(run.font_id, self.layout.font_size)?
|
||||
.size,
|
||||
};
|
||||
// if glyph_bounds.intersects(visible_bounds) {
|
||||
// if glyph.is_emoji {
|
||||
// cx.scene().push_image_glyph(scene::ImageGlyph {
|
||||
// font_id: run.font_id,
|
||||
// font_size: self.layout.font_size,
|
||||
// id: glyph.id,
|
||||
// origin: glyph_bounds.origin() + baseline_offset,
|
||||
// });
|
||||
// } else {
|
||||
// cx.scene().push_glyph(scene::Glyph {
|
||||
// font_id: run.font_id,
|
||||
// font_size: self.layout.font_size,
|
||||
// id: glyph.id,
|
||||
// origin: glyph_bounds.origin() + baseline_offset,
|
||||
// color,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((underline_origin, underline_style)) = underline.take() {
|
||||
let line_end_x = glyph_origin.x + self.layout.width - prev_position;
|
||||
cx.paint_underline(
|
||||
underline_origin,
|
||||
line_end_x - underline_origin.x,
|
||||
&underline_style,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
295
crates/gpui3/src/text_system/line_layout.rs
Normal file
295
crates/gpui3/src/text_system/line_layout.rs
Normal file
@ -0,0 +1,295 @@
|
||||
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LineLayout {
|
||||
pub font_size: Pixels,
|
||||
pub width: Pixels,
|
||||
pub ascent: Pixels,
|
||||
pub descent: Pixels,
|
||||
pub runs: Vec<ShapedRun>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShapedRun {
|
||||
pub font_id: FontId,
|
||||
pub glyphs: SmallVec<[ShapedGlyph; 8]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ShapedGlyph {
|
||||
pub id: GlyphId,
|
||||
pub position: Point<Pixels>,
|
||||
pub index: usize,
|
||||
pub is_emoji: bool,
|
||||
}
|
||||
|
||||
impl LineLayout {
|
||||
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
|
||||
if x >= self.width {
|
||||
None
|
||||
} else {
|
||||
for run in self.runs.iter().rev() {
|
||||
for glyph in run.glyphs.iter().rev() {
|
||||
if glyph.position.x <= x {
|
||||
return Some(glyph.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x_for_index(&self, index: usize) -> Pixels {
|
||||
for run in &self.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return glyph.position.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||
for run in &self.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return Some(run.font_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn compute_wrap_boundaries(
|
||||
&self,
|
||||
text: &str,
|
||||
wrap_width: Pixels,
|
||||
) -> SmallVec<[WrapBoundary; 1]> {
|
||||
let mut boundaries = SmallVec::new();
|
||||
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut last_candidate_ix = None;
|
||||
let mut last_candidate_x = px(0.);
|
||||
let mut last_boundary = WrapBoundary {
|
||||
run_ix: 0,
|
||||
glyph_ix: 0,
|
||||
};
|
||||
let mut last_boundary_x = px(0.);
|
||||
let mut prev_ch = '\0';
|
||||
let mut glyphs = self
|
||||
.runs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(run_ix, run)| {
|
||||
run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
|
||||
let character = text[glyph.index..].chars().next().unwrap();
|
||||
(
|
||||
WrapBoundary { run_ix, glyph_ix },
|
||||
character,
|
||||
glyph.position.x,
|
||||
)
|
||||
})
|
||||
})
|
||||
.peekable();
|
||||
|
||||
while let Some((boundary, ch, x)) = glyphs.next() {
|
||||
if ch == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = Some(boundary);
|
||||
last_candidate_x = x;
|
||||
}
|
||||
|
||||
if ch != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(boundary);
|
||||
}
|
||||
|
||||
let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
|
||||
let width = next_x - last_boundary_x;
|
||||
if width > wrap_width && boundary > last_boundary {
|
||||
if let Some(last_candidate_ix) = last_candidate_ix.take() {
|
||||
last_boundary = last_candidate_ix;
|
||||
last_boundary_x = last_candidate_x;
|
||||
} else {
|
||||
last_boundary = boundary;
|
||||
last_boundary_x = x;
|
||||
}
|
||||
|
||||
boundaries.push(last_boundary);
|
||||
}
|
||||
prev_ch = ch;
|
||||
}
|
||||
|
||||
boundaries
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Default, Debug)]
|
||||
pub struct WrappedLineLayout {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
pub layout: LineLayout,
|
||||
pub text: SharedString,
|
||||
pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct WrapBoundary {
|
||||
pub run_ix: usize,
|
||||
pub glyph_ix: usize,
|
||||
}
|
||||
|
||||
pub(crate) struct LineLayoutCache {
|
||||
prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
|
||||
curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
}
|
||||
|
||||
impl LineLayoutCache {
|
||||
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
Self {
|
||||
prev_frame: Mutex::new(HashMap::new()),
|
||||
curr_frame: RwLock::new(HashMap::new()),
|
||||
platform_text_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&self) {
|
||||
let mut prev_frame = self.prev_frame.lock();
|
||||
let mut curr_frame = self.curr_frame.write();
|
||||
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
||||
curr_frame.clear();
|
||||
}
|
||||
|
||||
pub fn layout_line(
|
||||
&self,
|
||||
text: &SharedString,
|
||||
font_size: Pixels,
|
||||
runs: &[FontRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
) -> Arc<WrappedLineLayout> {
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
font_size,
|
||||
runs,
|
||||
wrap_width,
|
||||
} as &dyn AsCacheKeyRef;
|
||||
let curr_frame = self.curr_frame.upgradable_read();
|
||||
if let Some(layout) = curr_frame.get(key) {
|
||||
return layout.clone();
|
||||
}
|
||||
|
||||
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
|
||||
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
|
||||
curr_frame.insert(key, layout.clone());
|
||||
layout
|
||||
} else {
|
||||
let layout = self.platform_text_system.layout_line(text, font_size, runs);
|
||||
let wrap_boundaries = wrap_width
|
||||
.map(|wrap_width| layout.compute_wrap_boundaries(text.as_ref(), wrap_width))
|
||||
.unwrap_or_default();
|
||||
let wrapped_line = Arc::new(WrappedLineLayout {
|
||||
layout,
|
||||
text: text.clone(),
|
||||
wrap_boundaries,
|
||||
});
|
||||
|
||||
let key = CacheKey {
|
||||
text: text.clone(),
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
wrap_width,
|
||||
};
|
||||
curr_frame.insert(key, wrapped_line.clone());
|
||||
wrapped_line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct FontRun {
|
||||
pub(crate) len: usize,
|
||||
pub(crate) font_id: FontId,
|
||||
}
|
||||
|
||||
trait AsCacheKeyRef {
|
||||
fn as_cache_key_ref(&self) -> CacheKeyRef;
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
struct CacheKey {
|
||||
text: SharedString,
|
||||
font_size: Pixels,
|
||||
runs: SmallVec<[FontRun; 1]>,
|
||||
wrap_width: Option<Pixels>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct CacheKeyRef<'a> {
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &'a [FontRun],
|
||||
wrap_width: Option<Pixels>,
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
|
||||
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
|
||||
self.as_cache_key_ref() == other.as_cache_key_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
|
||||
|
||||
impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_cache_key_ref().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsCacheKeyRef for CacheKey {
|
||||
fn as_cache_key_ref(&self) -> CacheKeyRef {
|
||||
CacheKeyRef {
|
||||
text: &self.text,
|
||||
font_size: self.font_size,
|
||||
runs: self.runs.as_slice(),
|
||||
wrap_width: self.wrap_width,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CacheKey {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_cache_key_ref().eq(&other.as_cache_key_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CacheKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_cache_key_ref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
|
||||
fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
|
||||
self as &dyn AsCacheKeyRef
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
|
||||
fn as_cache_key_ref(&self) -> CacheKeyRef {
|
||||
*self
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
|
||||
use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem};
|
||||
use collections::HashMap;
|
||||
use std::{iter, sync::Arc};
|
||||
|
||||
@ -46,7 +46,7 @@ impl LineWrapper {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = ix;
|
||||
last_candidate_width = width;
|
||||
}
|
||||
@ -87,79 +87,6 @@ impl LineWrapper {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wrap_shaped_line<'a>(
|
||||
&'a mut self,
|
||||
str: &'a str,
|
||||
line: &'a Line,
|
||||
wrap_width: Pixels,
|
||||
) -> impl Iterator<Item = ShapedBoundary> + 'a {
|
||||
let mut first_non_whitespace_ix = None;
|
||||
let mut last_candidate_ix = None;
|
||||
let mut last_candidate_x = px(0.);
|
||||
let mut last_wrap_ix = ShapedBoundary {
|
||||
run_ix: 0,
|
||||
glyph_ix: 0,
|
||||
};
|
||||
let mut last_wrap_x = px(0.);
|
||||
let mut prev_c = '\0';
|
||||
let mut glyphs = line
|
||||
.runs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(run_ix, run)| {
|
||||
run.glyphs()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(move |(glyph_ix, glyph)| {
|
||||
let character = str[glyph.index..].chars().next().unwrap();
|
||||
(
|
||||
ShapedBoundary { run_ix, glyph_ix },
|
||||
character,
|
||||
glyph.position.x,
|
||||
)
|
||||
})
|
||||
})
|
||||
.peekable();
|
||||
|
||||
iter::from_fn(move || {
|
||||
while let Some((ix, c, x)) = glyphs.next() {
|
||||
if c == '\n' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
|
||||
last_candidate_ix = Some(ix);
|
||||
last_candidate_x = x;
|
||||
}
|
||||
|
||||
if c != ' ' && first_non_whitespace_ix.is_none() {
|
||||
first_non_whitespace_ix = Some(ix);
|
||||
}
|
||||
|
||||
let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
|
||||
let width = next_x - last_wrap_x;
|
||||
if width > wrap_width && ix > last_wrap_ix {
|
||||
if let Some(last_candidate_ix) = last_candidate_ix.take() {
|
||||
last_wrap_ix = last_candidate_ix;
|
||||
last_wrap_x = last_candidate_x;
|
||||
} else {
|
||||
last_wrap_ix = ix;
|
||||
last_wrap_x = x;
|
||||
}
|
||||
|
||||
return Some(last_wrap_ix);
|
||||
}
|
||||
prev_c = c;
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn is_boundary(&self, prev: char, next: char) -> bool {
|
||||
(prev == ' ') && (next != ' ')
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn width_for_char(&mut self, c: char) -> Pixels {
|
||||
if (c as u32) < 128 {
|
||||
@ -182,8 +109,17 @@ impl LineWrapper {
|
||||
}
|
||||
|
||||
fn compute_width_for_char(&self, c: char) -> Pixels {
|
||||
let mut buffer = [0; 4];
|
||||
let buffer = c.encode_utf8(&mut buffer);
|
||||
self.platform_text_system
|
||||
.layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
|
||||
.layout_line(
|
||||
buffer,
|
||||
self.font_size,
|
||||
&[FontRun {
|
||||
len: 1,
|
||||
font_id: self.font_id,
|
||||
}],
|
||||
)
|
||||
.width
|
||||
}
|
||||
}
|
||||
@ -203,7 +139,7 @@ impl Boundary {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{font, App, RunStyle};
|
||||
use crate::{font, App};
|
||||
|
||||
#[test]
|
||||
fn test_wrap_line() {
|
||||
@ -268,62 +204,75 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
// todo!("move this to a test on TextSystem::layout_text")
|
||||
// todo! repeat this test
|
||||
#[test]
|
||||
fn test_wrap_shaped_line() {
|
||||
App::test().run(|cx| {
|
||||
let text_system = cx.text_system().clone();
|
||||
// #[test]
|
||||
// fn test_wrap_shaped_line() {
|
||||
// App::test().run(|cx| {
|
||||
// let text_system = cx.text_system().clone();
|
||||
|
||||
let normal = RunStyle {
|
||||
font: font("Helvetica"),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
let bold = RunStyle {
|
||||
font: font("Helvetica").bold(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
// let normal = TextRun {
|
||||
// len: 0,
|
||||
// font: font("Helvetica"),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let bold = TextRun {
|
||||
// len: 0,
|
||||
// font: font("Helvetica").bold(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
|
||||
let text = "aa bbb cccc ddddd eeee";
|
||||
let line = text_system
|
||||
.layout_line(
|
||||
text,
|
||||
px(16.),
|
||||
&[
|
||||
(4, normal.clone()),
|
||||
(5, bold.clone()),
|
||||
(6, normal.clone()),
|
||||
(1, bold.clone()),
|
||||
(7, normal.clone()),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
// impl TextRun {
|
||||
// fn with_len(&self, len: usize) -> Self {
|
||||
// let mut this = self.clone();
|
||||
// this.len = len;
|
||||
// this
|
||||
// }
|
||||
// }
|
||||
|
||||
let mut wrapper = LineWrapper::new(
|
||||
text_system.font_id(&normal.font).unwrap(),
|
||||
px(16.),
|
||||
text_system.platform_text_system.clone(),
|
||||
);
|
||||
assert_eq!(
|
||||
wrapper
|
||||
.wrap_shaped_line(text, &line, px(72.))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
ShapedBoundary {
|
||||
run_ix: 1,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 2,
|
||||
glyph_ix: 3
|
||||
},
|
||||
ShapedBoundary {
|
||||
run_ix: 4,
|
||||
glyph_ix: 2
|
||||
}
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
// let text = "aa bbb cccc ddddd eeee".into();
|
||||
// let lines = text_system
|
||||
// .layout_text(
|
||||
// &text,
|
||||
// px(16.),
|
||||
// &[
|
||||
// normal.with_len(4),
|
||||
// bold.with_len(5),
|
||||
// normal.with_len(6),
|
||||
// bold.with_len(1),
|
||||
// normal.with_len(7),
|
||||
// ],
|
||||
// None,
|
||||
// )
|
||||
// .unwrap();
|
||||
// let line = &lines[0];
|
||||
|
||||
// let mut wrapper = LineWrapper::new(
|
||||
// text_system.font_id(&normal.font).unwrap(),
|
||||
// px(16.),
|
||||
// text_system.platform_text_system.clone(),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// wrapper
|
||||
// .wrap_shaped_line(&text, &line, px(72.))
|
||||
// .collect::<Vec<_>>(),
|
||||
// &[
|
||||
// ShapedBoundary {
|
||||
// run_ix: 1,
|
||||
// glyph_ix: 3
|
||||
// },
|
||||
// ShapedBoundary {
|
||||
// run_ix: 2,
|
||||
// glyph_ix: 3
|
||||
// },
|
||||
// ShapedBoundary {
|
||||
// run_ix: 4,
|
||||
// glyph_ix: 2
|
||||
// }
|
||||
// ],
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
@ -1,153 +0,0 @@
|
||||
use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun};
|
||||
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub(crate) struct TextLayoutCache {
|
||||
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
|
||||
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
|
||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||
}
|
||||
|
||||
impl TextLayoutCache {
|
||||
pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
Self {
|
||||
prev_frame: Mutex::new(HashMap::new()),
|
||||
curr_frame: RwLock::new(HashMap::new()),
|
||||
platform_text_system: fonts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&self) {
|
||||
let mut prev_frame = self.prev_frame.lock();
|
||||
let mut curr_frame = self.curr_frame.write();
|
||||
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
|
||||
curr_frame.clear();
|
||||
}
|
||||
|
||||
pub fn layout_line<'a>(
|
||||
&'a self,
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &[(usize, FontId)],
|
||||
) -> Arc<ShapedLine> {
|
||||
let key = &CacheKeyRef {
|
||||
text,
|
||||
font_size,
|
||||
runs,
|
||||
} as &dyn CacheKey;
|
||||
let curr_frame = self.curr_frame.upgradable_read();
|
||||
if let Some(layout) = curr_frame.get(key) {
|
||||
return layout.clone();
|
||||
}
|
||||
|
||||
let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
|
||||
if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
|
||||
curr_frame.insert(key, layout.clone());
|
||||
layout
|
||||
} else {
|
||||
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
|
||||
let key = CacheKeyValue {
|
||||
text: text.into(),
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
};
|
||||
curr_frame.insert(key, layout.clone());
|
||||
layout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait CacheKey {
|
||||
fn key(&self) -> CacheKeyRef;
|
||||
}
|
||||
|
||||
impl<'a> PartialEq for (dyn CacheKey + 'a) {
|
||||
fn eq(&self, other: &dyn CacheKey) -> bool {
|
||||
self.key() == other.key()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eq for (dyn CacheKey + 'a) {}
|
||||
|
||||
impl<'a> Hash for (dyn CacheKey + 'a) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
struct CacheKeyValue {
|
||||
text: String,
|
||||
font_size: Pixels,
|
||||
runs: SmallVec<[(usize, FontId); 1]>,
|
||||
}
|
||||
|
||||
impl CacheKey for CacheKeyValue {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
CacheKeyRef {
|
||||
text: self.text.as_str(),
|
||||
font_size: self.font_size,
|
||||
runs: self.runs.as_slice(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CacheKeyValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key().eq(&other.key())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for CacheKeyValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
|
||||
fn borrow(&self) -> &(dyn CacheKey + 'a) {
|
||||
self as &dyn CacheKey
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
struct CacheKeyRef<'a> {
|
||||
text: &'a str,
|
||||
font_size: Pixels,
|
||||
runs: &'a [(usize, FontId)],
|
||||
}
|
||||
|
||||
impl<'a> CacheKey for CacheKeyRef<'a> {
|
||||
fn key(&self) -> CacheKeyRef {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hash for CacheKeyRef<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.text.hash(state);
|
||||
self.font_size.hash(state);
|
||||
for (len, font_id) in self.runs {
|
||||
len.hash(state);
|
||||
font_id.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ShapedBoundary {
|
||||
pub run_ix: usize,
|
||||
pub glyph_ix: usize,
|
||||
}
|
||||
|
||||
impl ShapedRun {
|
||||
pub fn glyphs(&self) -> &[ShapedGlyph] {
|
||||
&self.glyphs
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
AnyBox, AnyElement, BorrowWindow, Bounds, Element, ElementId, EntityId, Handle,
|
||||
IdentifiedElement, IntoAnyElement, LayoutId, Pixels, ViewContext, WindowContext,
|
||||
AnyBox, AnyElement, BorrowWindow, Bounds, Element, ElementId, EntityId, Handle, IntoAnyElement,
|
||||
LayoutId, Pixels, ViewContext, WindowContext,
|
||||
};
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
@ -57,7 +57,7 @@ impl<S: 'static + Send + Sync> Element for View<S> {
|
||||
type ViewState = ();
|
||||
type ElementState = AnyElement<S>;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Some(ElementId::View(self.state.id))
|
||||
}
|
||||
|
||||
@ -86,8 +86,6 @@ impl<S: 'static + Send + Sync> Element for View<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Send + Sync + 'static> IdentifiedElement for View<S> {}
|
||||
|
||||
struct EraseViewState<ViewState: 'static + Send + Sync, ParentViewState> {
|
||||
view: View<ViewState>,
|
||||
parent_view_state_type: PhantomData<ParentViewState>,
|
||||
@ -112,8 +110,8 @@ where
|
||||
type ViewState = ParentViewState;
|
||||
type ElementState = AnyBox;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
Element::element_id(&self.view)
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Element::id(&self.view)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
@ -148,7 +146,7 @@ impl<S: Send + Sync + 'static> ViewObject for View<S> {
|
||||
}
|
||||
|
||||
fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, AnyBox) {
|
||||
cx.with_element_id(IdentifiedElement::element_id(self), |cx| {
|
||||
cx.with_element_id(self.entity_id(), |cx| {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let mut element = (self.render)(state, cx);
|
||||
let layout_id = element.layout(state, cx);
|
||||
@ -159,7 +157,7 @@ impl<S: Send + Sync + 'static> ViewObject for View<S> {
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
|
||||
cx.with_element_id(IdentifiedElement::element_id(self), |cx| {
|
||||
cx.with_element_id(self.entity_id(), |cx| {
|
||||
self.state.update(cx, |state, cx| {
|
||||
let element = element.downcast_mut::<AnyElement<S>>().unwrap();
|
||||
element.paint(state, None, cx);
|
||||
@ -188,7 +186,7 @@ impl Element for AnyView {
|
||||
type ViewState = ();
|
||||
type ElementState = AnyBox;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Some(ElementId::View(self.view.lock().entity_id()))
|
||||
}
|
||||
|
||||
@ -233,8 +231,8 @@ where
|
||||
type ViewState = ParentViewState;
|
||||
type ElementState = AnyBox;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
Element::element_id(&self.view)
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Element::id(&self.view)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
@ -45,7 +45,7 @@ type MouseEventHandler =
|
||||
pub struct Window {
|
||||
handle: AnyWindowHandle,
|
||||
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
||||
pub(crate) display_id: DisplayId, // todo!("make private again?")
|
||||
display_id: DisplayId,
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
rem_size: Pixels,
|
||||
content_size: Size<Pixels>,
|
||||
@ -248,7 +248,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
style: Style,
|
||||
style: &Style,
|
||||
children: impl IntoIterator<Item = LayoutId>,
|
||||
) -> LayoutId {
|
||||
self.app.layout_id_buffer.clear();
|
||||
@ -600,7 +600,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||
|
||||
let mut root_view = cx.window.root_view.take().unwrap();
|
||||
|
||||
if let Some(element_id) = root_view.element_id() {
|
||||
if let Some(element_id) = root_view.id() {
|
||||
cx.with_element_state(element_id, |element_state, cx| {
|
||||
let element_state = draw_with_element_state(&mut root_view, element_state, cx);
|
||||
((), element_state)
|
||||
@ -1145,6 +1145,13 @@ impl From<SmallVec<[u32; 16]>> for StackingOrder {
|
||||
pub enum ElementId {
|
||||
View(EntityId),
|
||||
Number(usize),
|
||||
Name(SharedString),
|
||||
}
|
||||
|
||||
impl From<EntityId> for ElementId {
|
||||
fn from(id: EntityId) -> Self {
|
||||
ElementId::View(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for ElementId {
|
||||
@ -1158,3 +1165,15 @@ impl From<i32> for ElementId {
|
||||
Self::Number(id as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedString> for ElementId {
|
||||
fn from(name: SharedString) -> Self {
|
||||
ElementId::Name(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for ElementId {
|
||||
fn from(name: &'static str) -> Self {
|
||||
ElementId::Name(name.into())
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
|
||||
type ViewState = #state_type;
|
||||
type ElementState = gpui3::AnyElement<#state_type>;
|
||||
|
||||
fn element_id(&self) -> Option<gpui3::ElementId> {
|
||||
// todo!("What should element_id be here?")
|
||||
fn id(&self) -> Option<gpui3::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ fn generate_predefined_setter(
|
||||
let method = quote! {
|
||||
#[doc = #doc_string]
|
||||
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
|
||||
let mut style = self.declared_style();
|
||||
let style = self.style();
|
||||
#(#field_assignments)*
|
||||
self
|
||||
}
|
||||
@ -164,7 +164,7 @@ fn generate_custom_value_setter(
|
||||
let method = quote! {
|
||||
#[doc = #doc_string]
|
||||
fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui3::#length_type>) -> Self where Self: std::marker::Sized {
|
||||
let mut style = self.declared_style();
|
||||
let style = self.style();
|
||||
#(#field_assignments)*
|
||||
self
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refinement_refined_assignments: Vec<TokenStream2> = fields
|
||||
let refinement_refined_assigments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
@ -169,14 +169,37 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if refinement.#name.is_some() {
|
||||
self.#name = refinement.#name;
|
||||
if let Some(value) = refinement.#name {
|
||||
self.#name = Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let from_refinement_assigments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
let is_optional = is_optional_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
#name: value.#name.into(),
|
||||
}
|
||||
} else if is_optional {
|
||||
quote! {
|
||||
#name: value.#name.map(|v| v.into()),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name: value.#name.map(|v| v.into()).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let debug_impl = if impl_debug_on_refinement {
|
||||
let refinement_field_debugs: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
@ -243,11 +266,21 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
fn refined(mut self, refinement: Self::Refinement) -> Self {
|
||||
#( #refinement_refined_assignments )*
|
||||
#( #refinement_refined_assigments )*
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics From<#refinement_ident #ty_generics> for #ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn from(value: #refinement_ident #ty_generics) -> Self {
|
||||
Self {
|
||||
#( #from_refinement_assigments )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
@ -273,7 +306,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
|
||||
#debug_impl
|
||||
};
|
||||
|
||||
gen.into()
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use gpui3::{
|
||||
div, svg, view, AppContext, Context, Element, ElementId, IntoAnyElement, ParentElement,
|
||||
ScrollState, SharedString, StyleHelpers, Styled, View, ViewContext, WindowContext,
|
||||
div, svg, view, Active, AppContext, Context, Element, ElementId, Hover, IntoAnyElement,
|
||||
ParentElement, ScrollState, SharedString, Styled, View, ViewContext, WindowContext,
|
||||
};
|
||||
use ui::{theme, Theme};
|
||||
|
||||
@ -34,7 +34,7 @@ impl CollabPanel {
|
||||
.text_color(theme.middle.base.default.foreground)
|
||||
.border_color(theme.middle.base.default.border)
|
||||
.border()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
@ -132,8 +132,7 @@ impl CollabPanel {
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.active()
|
||||
.fill(theme.highest.accent.default.background)
|
||||
.active(|style| style.bg(theme.highest.accent.default.background))
|
||||
.child(div().flex().gap_1().text_sm().child(label))
|
||||
.child(
|
||||
div().flex().h_full().gap_1().items_center().child(
|
||||
@ -145,7 +144,7 @@ impl CollabPanel {
|
||||
})
|
||||
.w_3p5()
|
||||
.h_3p5()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
.text_color(theme.middle.variant.default.foreground),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -174,18 +173,19 @@ impl CollabPanel {
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.id(0)
|
||||
.id("avatar")
|
||||
// .uri(avatar_uri)
|
||||
.size_3p5()
|
||||
.rounded_full()
|
||||
.fill(theme.middle.positive.default.foreground)
|
||||
.bg(theme.middle.positive.default.foreground)
|
||||
.shadow()
|
||||
.group_hover("")
|
||||
.fill(theme.middle.negative.default.foreground)
|
||||
.hover()
|
||||
.fill(theme.middle.warning.default.foreground)
|
||||
.group_active("")
|
||||
.fill(theme.middle.accent.default.foreground),
|
||||
.group_hover("", |style| {
|
||||
style.bg(theme.middle.negative.default.foreground)
|
||||
})
|
||||
.hover(|style| style.bg(theme.middle.warning.default.foreground))
|
||||
.group_active("", |style| {
|
||||
style.bg(theme.middle.accent.default.foreground)
|
||||
}),
|
||||
)
|
||||
.child(label),
|
||||
)
|
||||
|
@ -1,2 +1,7 @@
|
||||
pub mod kitchen_sink;
|
||||
pub mod z_index;
|
||||
mod kitchen_sink;
|
||||
mod text;
|
||||
mod z_index;
|
||||
|
||||
pub use kitchen_sink::*;
|
||||
pub use text::*;
|
||||
pub use z_index::*;
|
||||
|
20
crates/storybook2/src/stories/text.rs
Normal file
20
crates/storybook2/src/stories/text.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use gpui3::{div, view, white, Context, ParentElement, Styled, View, WindowContext};
|
||||
|
||||
pub struct TextStory {
|
||||
text: View<()>,
|
||||
}
|
||||
|
||||
impl TextStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<()> {
|
||||
view(cx.entity(|cx| ()), |_, cx| {
|
||||
div()
|
||||
.size_full()
|
||||
.bg(white())
|
||||
.child(concat!(
|
||||
"The quick brown fox jumps over the lazy dog. ",
|
||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
@ -59,7 +59,7 @@ impl<S: 'static + Send + Sync> ZIndexStory<S> {
|
||||
}
|
||||
}
|
||||
|
||||
trait Styles: StyleHelpers {
|
||||
trait Styles: Styled + Sized {
|
||||
// Trailing `_` is so we don't collide with `block` style `StyleHelpers`.
|
||||
fn block_(self) -> Self {
|
||||
self.absolute()
|
||||
@ -69,7 +69,7 @@ trait Styles: StyleHelpers {
|
||||
}
|
||||
|
||||
fn blue(self) -> Self {
|
||||
self.fill(rgb::<Hsla>(0xe5e8fc))
|
||||
self.bg(rgb::<Hsla>(0xe5e8fc))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0x112382))
|
||||
// HACK: Simulate `line-height: 55px`.
|
||||
@ -79,7 +79,7 @@ trait Styles: StyleHelpers {
|
||||
}
|
||||
|
||||
fn red(self) -> Self {
|
||||
self.fill(rgb::<Hsla>(0xfce5e7))
|
||||
self.bg(rgb::<Hsla>(0xfce5e7))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0xe3a1a7))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
@ -115,7 +115,7 @@ impl<S: 'static + Send + Sync> ZIndexExample<S> {
|
||||
.left(px(15.))
|
||||
.w(px(180.))
|
||||
.h(px(230.))
|
||||
.fill(rgb::<Hsla>(0xfcfbe5))
|
||||
.bg(rgb::<Hsla>(0xfcfbe5))
|
||||
.text_color(rgb::<Hsla>(0x000000))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0xe3e0a1))
|
||||
|
@ -1,12 +1,12 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::stories::*;
|
||||
use anyhow::anyhow;
|
||||
use clap::builder::PossibleValue;
|
||||
use clap::ValueEnum;
|
||||
use gpui3::{view, AnyView, Context};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
||||
|
||||
use ui::prelude::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
||||
@ -18,6 +18,7 @@ pub enum ElementStory {
|
||||
Icon,
|
||||
Input,
|
||||
Label,
|
||||
Text,
|
||||
ZIndex,
|
||||
}
|
||||
|
||||
@ -43,10 +44,10 @@ impl ElementStory {
|
||||
Self::Label => {
|
||||
view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().into_any()).into_any()
|
||||
}
|
||||
Self::ZIndex => view(cx.entity(|cx| ()), |_, _| {
|
||||
crate::stories::z_index::ZIndexStory::new().into_any()
|
||||
})
|
||||
.into_any(),
|
||||
Self::Text => TextStory::view(cx).into_any(),
|
||||
Self::ZIndex => {
|
||||
view(cx.entity(|cx| ()), |_, _| ZIndexStory::new().into_any()).into_any()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,9 +213,7 @@ impl StorySelector {
|
||||
match self {
|
||||
Self::Element(element_story) => element_story.story(cx),
|
||||
Self::Component(component_story) => component_story.story(cx),
|
||||
Self::KitchenSink => {
|
||||
crate::stories::kitchen_sink::KitchenSinkStory::view(cx).into_any()
|
||||
}
|
||||
Self::KitchenSink => KitchenSinkStory::view(cx).into_any(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
themes::rose_pine,
|
||||
};
|
||||
use gpui3::{
|
||||
div, img, svg, view, Context, Element, ParentElement, StyleHelpers, Styled, View, ViewContext,
|
||||
div, img, svg, view, Context, Element, Hover, ParentElement, Styled, View, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use ui::{theme, themed};
|
||||
@ -34,18 +34,18 @@ impl Workspace {
|
||||
.w_full()
|
||||
.h_5()
|
||||
.mt_10()
|
||||
.fill(theme.middle.warning.default.foreground)
|
||||
.bg(theme.middle.warning.default.foreground)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_center()
|
||||
.child(
|
||||
div()
|
||||
.size_5()
|
||||
.fill(theme.middle.negative.default.foreground)
|
||||
.group_hover("")
|
||||
.fill(theme.middle.positive.default.foreground)
|
||||
.hover()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
.bg(theme.middle.negative.default.foreground)
|
||||
.group_hover("", |style| {
|
||||
style.bg(theme.middle.positive.default.foreground)
|
||||
})
|
||||
.hover(|style| style.bg(theme.middle.variant.default.foreground)),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -63,7 +63,7 @@ impl Workspace {
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
.child(titlebar(cx))
|
||||
.child(
|
||||
div()
|
||||
@ -91,7 +91,7 @@ pub fn titlebar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Eleme
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.child(this.left_group(cx))
|
||||
.child(this.right_group(cx))
|
||||
}
|
||||
@ -108,7 +108,7 @@ impl Titlebar {
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.child(self.left_group(cx))
|
||||
.child(self.right_group(cx))
|
||||
}
|
||||
@ -131,25 +131,25 @@ impl Titlebar {
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.positive.default.foreground),
|
||||
div().w_3().h_3().rounded_full().bg(theme
|
||||
.lowest
|
||||
.positive
|
||||
.default
|
||||
.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.warning.default.foreground),
|
||||
div().w_3().h_3().rounded_full().bg(theme
|
||||
.lowest
|
||||
.warning
|
||||
.default
|
||||
.foreground),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_3()
|
||||
.h_3()
|
||||
.rounded_full()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
div().w_3().h_3().rounded_full().bg(theme
|
||||
.lowest
|
||||
.negative
|
||||
.default
|
||||
.foreground),
|
||||
),
|
||||
)
|
||||
// === Project Info === //
|
||||
@ -215,12 +215,12 @@ impl Titlebar {
|
||||
svg()
|
||||
.path("icons/exit.svg")
|
||||
.size_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
.child(div().w_px().h_3().bg(theme.lowest.base.default.border))
|
||||
// === Comms === //
|
||||
.child(
|
||||
div().child(
|
||||
@ -245,7 +245,7 @@ impl Titlebar {
|
||||
svg()
|
||||
.path("icons/mic.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@ -265,7 +265,7 @@ impl Titlebar {
|
||||
svg()
|
||||
.path("icons/speaker-loud.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@ -285,12 +285,12 @@ impl Titlebar {
|
||||
svg()
|
||||
.path("icons/desktop.svg")
|
||||
.size_3p5()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(div().w_px().h_3().fill(theme.lowest.base.default.border))
|
||||
.child(div().w_px().h_3().bg(theme.lowest.base.default.border))
|
||||
// User Group
|
||||
.child(
|
||||
div().child(
|
||||
@ -311,14 +311,14 @@ impl Titlebar {
|
||||
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||
.size_4()
|
||||
.rounded_md()
|
||||
.fill(theme.middle.on.default.foreground),
|
||||
.bg(theme.middle.on.default.foreground),
|
||||
)
|
||||
.child(
|
||||
svg()
|
||||
.path("icons/caret_down.svg")
|
||||
.w_2()
|
||||
.h_2()
|
||||
.fill(theme.lowest.variant.default.foreground),
|
||||
.text_color(theme.lowest.variant.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -341,7 +341,7 @@ mod statusbar {
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
// .child(left_group(cx))
|
||||
// .child(right_group(cx))
|
||||
}
|
||||
@ -374,7 +374,7 @@ mod statusbar {
|
||||
.path("icons/project.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@ -389,7 +389,7 @@ mod statusbar {
|
||||
.path("icons/conversations.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@ -404,7 +404,7 @@ mod statusbar {
|
||||
.path("icons/file_icons/notebook.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
.text_color(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -432,7 +432,7 @@ mod statusbar {
|
||||
.path("icons/error.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.negative.default.foreground),
|
||||
.text_color(theme.lowest.negative.default.foreground),
|
||||
)
|
||||
.child(div().text_sm().child("2")),
|
||||
)
|
||||
@ -473,7 +473,7 @@ mod statusbar {
|
||||
.path("icons/check_circle.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.base.default.foreground),
|
||||
.text_color(theme.lowest.base.default.foreground),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@ -488,7 +488,7 @@ mod statusbar {
|
||||
.path("icons/copilot.svg")
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.lowest.accent.default.foreground),
|
||||
.text_color(theme.lowest.accent.default.foreground),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -31,7 +31,11 @@ impl<S: 'static + Send + Sync + Clone> Breadcrumb<S> {
|
||||
.text_color(HighlightColor::Default.hsla(theme))
|
||||
}
|
||||
|
||||
fn render(&mut self, view_state: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
view_state: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
let theme = theme(cx);
|
||||
|
||||
let symbols_len = self.symbols.len();
|
||||
@ -43,8 +47,7 @@ impl<S: 'static + Send + Sync + Clone> Breadcrumb<S> {
|
||||
.text_sm()
|
||||
.text_color(theme.middle.base.default.foreground)
|
||||
.rounded_md()
|
||||
.hover()
|
||||
.fill(theme.highest.base.hovered.background)
|
||||
.hover(|style| style.bg(theme.highest.base.hovered.background))
|
||||
.child(self.path.clone().to_str().unwrap().to_string())
|
||||
.child(if !self.symbols.is_empty() {
|
||||
self.render_separator(&theme)
|
||||
@ -99,7 +102,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, view_state: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
view_state: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
let theme = theme(cx);
|
||||
|
||||
Story::container(cx)
|
||||
|
@ -179,7 +179,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
|
||||
};
|
||||
|
||||
h_stack()
|
||||
.fill(line_background)
|
||||
.bg(line_background)
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.px_1()
|
||||
@ -201,7 +201,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
|
||||
),
|
||||
)
|
||||
})
|
||||
.child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx)))
|
||||
.child(div().mx_0p5().w_1().h_full().bg(row.status.hsla(cx)))
|
||||
.children(row.line.map(|line| {
|
||||
div()
|
||||
.flex()
|
||||
@ -232,7 +232,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.fill(theme.highest.base.default.background)
|
||||
.bg(theme.highest.base.default.background)
|
||||
.children(rows)
|
||||
}
|
||||
}
|
||||
@ -263,7 +263,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
let theme = theme(cx);
|
||||
|
||||
Story::container(cx)
|
||||
|
@ -30,7 +30,7 @@ impl BufferSearch {
|
||||
let theme = theme(cx);
|
||||
|
||||
h_stack()
|
||||
.fill(theme.highest.base.default.background)
|
||||
.bg(theme.highest.base.default.background)
|
||||
.p_2()
|
||||
.child(
|
||||
h_stack()
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use gpui3::{img, svg, ArcCow};
|
||||
use gpui3::{img, svg, SharedString};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::theme::{theme, Theme};
|
||||
@ -29,14 +29,14 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
|
||||
|
||||
v_stack()
|
||||
.h_full()
|
||||
.fill(color.surface)
|
||||
.bg(color.surface)
|
||||
.child(
|
||||
v_stack()
|
||||
.w_full()
|
||||
.overflow_y_scroll(self.scroll_state.clone())
|
||||
.child(
|
||||
div()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.pb_1()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.border_b()
|
||||
@ -100,7 +100,7 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
|
||||
|
||||
fn list_section_header(
|
||||
&self,
|
||||
label: impl Into<ArcCow<'static, str>>,
|
||||
label: impl Into<SharedString>,
|
||||
expanded: bool,
|
||||
theme: &Theme,
|
||||
) -> impl Element<ViewState = S> {
|
||||
@ -121,15 +121,15 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
|
||||
})
|
||||
.w_3p5()
|
||||
.h_3p5()
|
||||
.fill(theme.middle.variant.default.foreground),
|
||||
.text_color(theme.middle.variant.default.foreground),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn list_item(
|
||||
&self,
|
||||
avatar_uri: impl Into<ArcCow<'static, str>>,
|
||||
label: impl Into<ArcCow<'static, str>>,
|
||||
avatar_uri: impl Into<SharedString>,
|
||||
label: impl Into<SharedString>,
|
||||
theme: &Theme,
|
||||
) -> impl Element<ViewState = S> {
|
||||
div()
|
||||
@ -137,10 +137,8 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
|
||||
.px_2()
|
||||
.flex()
|
||||
.items_center()
|
||||
.hover()
|
||||
.fill(theme.lowest.variant.hovered.background)
|
||||
// .active()
|
||||
// .fill(theme.lowest.variant.pressed.background)
|
||||
.hover(|style| style.bg(theme.lowest.variant.hovered.background))
|
||||
// .active(|style| style.fill(theme.lowest.variant.pressed.background))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
@ -148,11 +146,11 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
|
||||
.gap_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
img()
|
||||
.uri(avatar_uri)
|
||||
.size_3p5()
|
||||
.rounded_full()
|
||||
.fill(theme.middle.positive.default.foreground),
|
||||
img().uri(avatar_uri).size_3p5().rounded_full().bg(theme
|
||||
.middle
|
||||
.positive
|
||||
.default
|
||||
.foreground),
|
||||
)
|
||||
.child(label.into()),
|
||||
)
|
||||
@ -180,7 +178,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, CollabPanel<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -48,7 +48,7 @@ impl<S: 'static + Send + Sync + Clone> ContextMenu<S> {
|
||||
|
||||
v_stack()
|
||||
.flex()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.border()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.child(
|
||||
@ -87,7 +87,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, ContextMenu<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -76,7 +76,7 @@ impl<S: 'static + Send + Sync> IconButton<S> {
|
||||
|
||||
let mut div = div();
|
||||
if self.variant == ButtonVariant::Filled {
|
||||
div = div.fill(theme.highest.on.default.background);
|
||||
div = div.bg(theme.highest.on.default.background);
|
||||
}
|
||||
|
||||
if let Some(click_handler) = self.handlers.click.clone() {
|
||||
@ -91,8 +91,7 @@ impl<S: 'static + Send + Sync> IconButton<S> {
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.hover()
|
||||
.fill(theme.highest.base.hovered.background)
|
||||
.hover(|style| style.bg(theme.highest.base.hovered.background))
|
||||
// .active()
|
||||
// .fill(theme.highest.base.pressed.background)
|
||||
.child(IconElement::new(self.icon).color(icon_color))
|
||||
|
@ -81,7 +81,7 @@ impl<S: 'static + Send + Sync> Key<S> {
|
||||
.rounded_md()
|
||||
.text_sm()
|
||||
.text_color(theme.lowest.on.default.foreground)
|
||||
.fill(theme.lowest.on.default.background)
|
||||
.bg(theme.lowest.on.default.background)
|
||||
.child(self.key.clone())
|
||||
}
|
||||
}
|
||||
@ -189,7 +189,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
let all_modifier_permutations = ModifierKey::iter().permutations(2);
|
||||
|
||||
Story::container(cx)
|
||||
|
@ -106,7 +106,7 @@ impl<S: 'static + Send + Sync + Clone> ListHeader<S> {
|
||||
h_stack()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.fill(color.surface)
|
||||
.bg(color.surface)
|
||||
.when(self.state == InteractionState::Focused, |this| {
|
||||
this.border().border_color(color.border_focused)
|
||||
})
|
||||
@ -398,7 +398,7 @@ impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
|
||||
div()
|
||||
.relative()
|
||||
.group("")
|
||||
.fill(color.surface)
|
||||
.bg(color.surface)
|
||||
.when(self.state == InteractionState::Focused, |this| {
|
||||
this.border().border_color(color.border_focused)
|
||||
})
|
||||
@ -412,12 +412,11 @@ impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
|
||||
.h_full()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.group_hover("")
|
||||
.fill(color.border_focused)
|
||||
.group_hover("", |style| style.bg(color.border_focused))
|
||||
.child(
|
||||
h_stack()
|
||||
.child(div().w_px().h_full())
|
||||
.child(div().w_px().h_full().fill(color.border)),
|
||||
.child(div().w_px().h_full().bg(color.border)),
|
||||
)
|
||||
}))
|
||||
.flex()
|
||||
@ -446,7 +445,7 @@ impl<S: 'static + Send + Sync> ListSeparator<S> {
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
let color = ThemeColor::new(cx);
|
||||
|
||||
div().h_px().w_full().fill(color.border)
|
||||
div().h_px().w_full().bg(color.border)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ impl<S: 'static + Send + Sync + Clone> MultiBuffer<S> {
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.p_4()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.child(Label::new("main.rs").size(LabelSize::Small))
|
||||
.child(IconButton::new(Icon::ArrowUpRight)),
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use gpui3::{Element, ParentElement, StyleHelpers, ViewContext};
|
||||
use gpui3::{Element, ParentElement, Styled, ViewContext};
|
||||
|
||||
use crate::{
|
||||
h_stack, v_stack, Button, Icon, IconButton, IconElement, Label, ThemeColor, Toast, ToastOrigin,
|
||||
|
@ -53,7 +53,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
|
||||
v_stack()
|
||||
.w_96()
|
||||
.rounded_lg()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.border()
|
||||
.border_color(theme.lowest.base.default.border)
|
||||
.child(
|
||||
@ -64,7 +64,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
|
||||
Label::new(self.input_placeholder).color(LabelColor::Placeholder),
|
||||
),
|
||||
))
|
||||
.child(div().h_px().w_full().fill(theme.lowest.base.default.border))
|
||||
.child(div().h_px().w_full().bg(theme.lowest.base.default.border))
|
||||
.child(
|
||||
v_stack()
|
||||
.py_0p5()
|
||||
@ -89,8 +89,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
|
||||
.px_2()
|
||||
.py_0p5()
|
||||
.rounded_lg()
|
||||
.hover()
|
||||
.fill(theme.lowest.base.hovered.background)
|
||||
.hover(|style| style.bg(theme.lowest.base.hovered.background))
|
||||
// .active()
|
||||
// .fill(theme.lowest.base.pressed.background)
|
||||
.child(item.clone())
|
||||
@ -172,7 +171,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Palette<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -109,7 +109,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
|
||||
.h_full()
|
||||
// .w(current_width)
|
||||
.w_64()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
.border_r()
|
||||
.border_color(theme.middle.base.default.border);
|
||||
}
|
||||
@ -119,7 +119,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
|
||||
.h_full()
|
||||
// .w(current_width)
|
||||
.w_64()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
.border_l()
|
||||
.border_color(theme.middle.base.default.border);
|
||||
}
|
||||
@ -129,7 +129,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
|
||||
.w_full()
|
||||
// .h(current_width)
|
||||
.h_64()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
.border_t()
|
||||
.border_color(theme.middle.base.default.border);
|
||||
}
|
||||
@ -140,9 +140,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> ParentElement for Panel<S> {
|
||||
type State = S;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ impl<S: 'static + Send + Sync> Pane<S> {
|
||||
div()
|
||||
.flex()
|
||||
.flex_initial()
|
||||
.fill(self.fill)
|
||||
.bg(self.fill)
|
||||
.w(self.size.width)
|
||||
.h(self.size.height)
|
||||
.overflow_y_scroll(self.scroll_state.clone())
|
||||
@ -57,9 +57,7 @@ impl<S: 'static + Send + Sync> Pane<S> {
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> ParentElement for Pane<S> {
|
||||
type State = S;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
@ -101,7 +99,7 @@ impl<S: 'static + Send + Sync> PaneGroup<S> {
|
||||
.gap_px()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.children(self.panes.iter_mut().map(|pane| pane.render(view, cx)));
|
||||
|
||||
if self.split_direction == SplitDirection::Horizontal {
|
||||
@ -118,7 +116,7 @@ impl<S: 'static + Send + Sync> PaneGroup<S> {
|
||||
.gap_px()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.children(self.groups.iter_mut().map(|group| group.render(view, cx)));
|
||||
|
||||
if self.split_direction == SplitDirection::Horizontal {
|
||||
|
@ -39,13 +39,11 @@ impl<S: 'static + Send + Sync> PlayerStack<S> {
|
||||
.gap_px()
|
||||
.justify_center()
|
||||
.child(
|
||||
div().flex().justify_center().w_full().child(
|
||||
div()
|
||||
.w_4()
|
||||
.h_0p5()
|
||||
.rounded_sm()
|
||||
.fill(player.cursor_color(cx)),
|
||||
),
|
||||
div()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.w_full()
|
||||
.child(div().w_4().h_0p5().rounded_sm().bg(player.cursor_color(cx))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@ -55,7 +53,7 @@ impl<S: 'static + Send + Sync> PlayerStack<S> {
|
||||
.h_6()
|
||||
.pl_1()
|
||||
.rounded_lg()
|
||||
.fill(if followers.is_none() {
|
||||
.bg(if followers.is_none() {
|
||||
system_color.transparent
|
||||
} else {
|
||||
player.selection_color(cx)
|
||||
|
@ -29,7 +29,7 @@ impl<S: 'static + Send + Sync + Clone> ProjectPanel<S> {
|
||||
.flex_col()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.fill(color.surface)
|
||||
.bg(color.surface)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
|
@ -96,7 +96,7 @@ impl StatusBar {
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.child(self.left_tools(view, &theme))
|
||||
.child(self.right_tools(view, &theme))
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.fill(if self.current {
|
||||
.bg(if self.current {
|
||||
theme.highest.base.default.background
|
||||
} else {
|
||||
theme.middle.base.default.background
|
||||
@ -157,7 +157,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
let git_statuses = GitStatus::iter();
|
||||
let fs_statuses = FileSystemStatus::iter();
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
// Left Side
|
||||
.child(
|
||||
div()
|
||||
@ -105,7 +105,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TabBar<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -32,7 +32,7 @@ impl<S: 'static + Send + Sync + Clone> Terminal<S> {
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.fill(theme.middle.base.default.background)
|
||||
.bg(theme.middle.base.default.background)
|
||||
.child(
|
||||
div().px_1().flex().flex_none().gap_2().child(
|
||||
div()
|
||||
|
@ -108,7 +108,7 @@ impl TitleBar {
|
||||
.justify_between()
|
||||
.w_full()
|
||||
.h_8()
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
|
@ -56,15 +56,13 @@ impl<S: 'static + Send + Sync> Toast<S> {
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.overflow_hidden()
|
||||
.fill(color.elevated_surface)
|
||||
.bg(color.elevated_surface)
|
||||
.children(self.children.drain(..))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: 'static + Send + Sync> ParentElement for Toast<S> {
|
||||
type State = S;
|
||||
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ impl<S: 'static + Send + Sync> Toolbar<S> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.fill(theme.highest.base.default.background)
|
||||
.bg(theme.highest.base.default.background)
|
||||
.p_2()
|
||||
.flex()
|
||||
.justify_between()
|
||||
|
@ -37,7 +37,7 @@ impl<S: 'static + Send + Sync> TrafficLight<S> {
|
||||
(false, _) => theme.lowest.base.active.background,
|
||||
};
|
||||
|
||||
div().w_3().h_3().rounded_full().fill(fill)
|
||||
div().w_3().h_3().rounded_full().bg(fill)
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +104,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, TrafficLights<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -151,7 +151,7 @@ impl Workspace {
|
||||
.justify_start()
|
||||
.items_start()
|
||||
.text_color(theme.lowest.base.default.foreground)
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
.child(self.title_bar.clone())
|
||||
.child(
|
||||
div()
|
||||
|
@ -39,7 +39,7 @@ impl<S: 'static + Send + Sync> Avatar<S> {
|
||||
|
||||
img.uri(self.src.clone())
|
||||
.size_4()
|
||||
.fill(theme.middle.warning.default.foreground)
|
||||
.bg(theme.middle.warning.default.foreground)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Avatar<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -162,7 +162,7 @@ impl<S: 'static + Send + Sync + Clone> Button<S> {
|
||||
.rounded_md()
|
||||
.border()
|
||||
.border_color(border_color)
|
||||
.fill(self.background_color(cx));
|
||||
.bg(self.background_color(cx));
|
||||
|
||||
match (self.icon, self.icon_position) {
|
||||
(Some(_), Some(IconPosition::Left)) => {
|
||||
|
@ -183,7 +183,10 @@ impl<S: 'static + Send + Sync> IconElement<S> {
|
||||
IconSize::Large => svg().size_4(),
|
||||
};
|
||||
|
||||
sized_svg.flex_none().path(self.icon.path()).fill(fill)
|
||||
sized_svg
|
||||
.flex_none()
|
||||
.path(self.icon.path())
|
||||
.text_color(fill)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,12 +87,13 @@ impl<S: 'static + Send + Sync> Input<S> {
|
||||
.px_2()
|
||||
.border()
|
||||
.border_color(border_color_default)
|
||||
.fill(background_color_default)
|
||||
.hover()
|
||||
.border_color(border_color_hover)
|
||||
// .active()
|
||||
// .border_color(border_color_active)
|
||||
.fill(background_color_active)
|
||||
.bg(background_color_default)
|
||||
.hover(|style| {
|
||||
style
|
||||
.border_color(border_color_hover)
|
||||
.bg(background_color_active)
|
||||
})
|
||||
// .active(|a| .border_color(border_color_active))
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(
|
||||
@ -128,7 +129,11 @@ mod stories {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
fn render(
|
||||
&mut self,
|
||||
_view: &mut S,
|
||||
cx: &mut ViewContext<S>,
|
||||
) -> impl Element<ViewState = S> {
|
||||
Story::container(cx)
|
||||
.child(Story::title_for::<_, Input<S>>(cx))
|
||||
.child(Story::label(cx, "Default"))
|
||||
|
@ -141,7 +141,7 @@ impl<S: 'static + Send + Sync + Clone> Label<S> {
|
||||
.my_auto()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.fill(LabelColor::Hidden.hsla(cx)),
|
||||
.bg(LabelColor::Hidden.hsla(cx)),
|
||||
)
|
||||
})
|
||||
.children(runs.into_iter().map(|run| {
|
||||
|
@ -2,7 +2,7 @@ use gpui3::{div, Div};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub trait Stack: StyleHelpers {
|
||||
pub trait Stack: Styled + Sized {
|
||||
/// Horizontally stacks elements.
|
||||
fn h_stack(self) -> Self {
|
||||
self.flex().flex_row().items_center()
|
||||
|
@ -18,6 +18,6 @@ impl<S: 'static + Send + Sync> ToolDivider<S> {
|
||||
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div().w_px().h_3().fill(theme.lowest.base.default.border)
|
||||
div().w_px().h_3().bg(theme.lowest.base.default.border)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub use gpui3::{
|
||||
div, Element, IntoAnyElement, ParentElement, ScrollState, StyleHelpers, Styled, ViewContext,
|
||||
div, Click, Element, Hover, IntoAnyElement, ParentElement, ScrollState, Styled, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,7 @@ impl Story {
|
||||
.pt_2()
|
||||
.px_4()
|
||||
.font("Zed Mono Extended")
|
||||
.fill(theme.lowest.base.default.background)
|
||||
.bg(theme.lowest.base.default.background)
|
||||
}
|
||||
|
||||
pub fn title<S: 'static + Send + Sync>(
|
||||
|
@ -160,7 +160,7 @@ impl<E: Element> Element for Themed<E> {
|
||||
type ViewState = E::ViewState;
|
||||
type ElementState = E::ElementState;
|
||||
|
||||
fn element_id(&self) -> Option<gpui3::ElementId> {
|
||||
fn id(&self) -> Option<gpui3::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user