Merge branch 'mouse-events' into project-panel-context-menu

This commit is contained in:
Antonio Scandurra 2022-05-27 12:00:06 +02:00
commit 9099c40364
26 changed files with 816 additions and 523 deletions

View file

@ -75,9 +75,9 @@ impl ChatPanel {
}) })
}); });
let mut message_list = ListState::new(0, Orientation::Bottom, 1000., { let mut message_list = ListState::new(0, Orientation::Bottom, 1000., cx, {
let this = cx.weak_handle(); let this = cx.weak_handle();
move |ix, cx| { move |_, ix, cx| {
let this = this.upgrade(cx).unwrap().read(cx); let this = this.upgrade(cx).unwrap().read(cx);
let message = this.active_channel.as_ref().unwrap().0.read(cx).message(ix); let message = this.active_channel.as_ref().unwrap().0.read(cx).message(ix);
this.render_message(message, cx) this.render_message(message, cx)

View file

@ -1,9 +1,9 @@
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, actions,
elements::{ChildView, Flex, Label, MouseState, ParentElement}, elements::{ChildView, Flex, Label, ParentElement},
keymap::Keystroke, keymap::Keystroke,
Action, Element, Entity, MutableAppContext, View, ViewContext, ViewHandle, Action, Element, Entity, MouseState, MutableAppContext, View, ViewContext, ViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use settings::Settings; use settings::Settings;
@ -203,7 +203,7 @@ impl PickerDelegate for CommandPalette {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &MouseState, mouse_state: MouseState,
selected: bool, selected: bool,
cx: &gpui::AppContext, cx: &gpui::AppContext,
) -> gpui::ElementBox { ) -> gpui::ElementBox {

View file

@ -1,7 +1,7 @@
use client::{ContactRequestStatus, User, UserStore}; use client::{ContactRequestStatus, User, UserStore};
use gpui::{ use gpui::{
actions, elements::*, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, actions, elements::*, Entity, ModelHandle, MouseState, MutableAppContext, RenderContext, Task,
ViewContext, ViewHandle, View, ViewContext, ViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use settings::Settings; use settings::Settings;
@ -105,7 +105,7 @@ impl PickerDelegate for ContactFinder {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &MouseState, mouse_state: MouseState,
selected: bool, selected: bool,
cx: &gpui::AppContext, cx: &gpui::AppContext,
) -> ElementBox { ) -> ElementBox {

View file

@ -12,8 +12,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
impl_actions, impl_internal_actions, impl_actions, impl_internal_actions,
platform::CursorStyle, platform::CursorStyle,
AppContext, ClipboardItem, Element, ElementBox, Entity, LayoutContext, ModelHandle, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use join_project_notification::JoinProjectNotification; use join_project_notification::JoinProjectNotification;
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
@ -181,11 +181,8 @@ impl ContactsPanel {
.detach(); .detach();
let mut this = Self { let mut this = Self {
list_state: ListState::new(0, Orientation::Top, 1000., { list_state: ListState::new(0, Orientation::Top, 1000., cx, {
let this = cx.weak_handle(); move |this, ix, cx| {
move |ix, cx| {
let this = this.upgrade(cx).unwrap();
let this = this.read(cx);
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.contacts_panel; let theme = &theme.contacts_panel;
let current_user_id = let current_user_id =
@ -255,11 +252,11 @@ impl ContactsPanel {
theme: &theme::ContactsPanel, theme: &theme::ContactsPanel,
is_selected: bool, is_selected: bool,
is_collapsed: bool, is_collapsed: bool,
cx: &mut LayoutContext, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
enum Header {} enum Header {}
let header_style = theme.header_row.style_for(&Default::default(), is_selected); let header_style = theme.header_row.style_for(Default::default(), is_selected);
let text = match section { let text = match section {
Section::Requests => "Requests", Section::Requests => "Requests",
Section::Online => "Online", Section::Online => "Online",
@ -331,11 +328,7 @@ impl ContactsPanel {
.constrained() .constrained()
.with_height(theme.row_height) .with_height(theme.row_height)
.contained() .contained()
.with_style( .with_style(*theme.contact_row.style_for(Default::default(), is_selected))
*theme
.contact_row
.style_for(&Default::default(), is_selected),
)
.boxed() .boxed()
} }
@ -346,7 +339,7 @@ impl ContactsPanel {
theme: &theme::ContactsPanel, theme: &theme::ContactsPanel,
is_last_project: bool, is_last_project: bool,
is_selected: bool, is_selected: bool,
cx: &mut LayoutContext, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let project = &contact.projects[project_index]; let project = &contact.projects[project_index];
let project_id = project.id; let project_id = project.id;
@ -459,7 +452,7 @@ impl ContactsPanel {
theme: &theme::ContactsPanel, theme: &theme::ContactsPanel,
is_incoming: bool, is_incoming: bool,
is_selected: bool, is_selected: bool,
cx: &mut LayoutContext, cx: &mut RenderContext<ContactsPanel>,
) -> ElementBox { ) -> ElementBox {
enum Decline {} enum Decline {}
enum Accept {} enum Accept {}
@ -558,11 +551,7 @@ impl ContactsPanel {
row.constrained() row.constrained()
.with_height(theme.row_height) .with_height(theme.row_height)
.contained() .contained()
.with_style( .with_style(*theme.contact_row.style_for(Default::default(), is_selected))
*theme
.contact_row
.style_for(&Default::default(), is_selected),
)
.boxed() .boxed()
} }

View file

@ -185,10 +185,9 @@ impl ContextMenu {
.with_children(self.items.iter().enumerate().map(|(ix, item)| { .with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item { match item {
ContextMenuItem::Item { label, .. } => { ContextMenuItem::Item { label, .. } => {
let style = style.item.style_for( let style = style
&Default::default(), .item
Some(ix) == self.selected_index, .style_for(Default::default(), Some(ix) == self.selected_index);
);
Label::new(label.to_string(), style.label.clone()) Label::new(label.to_string(), style.label.clone())
.contained() .contained()
.with_style(style.container) .with_style(style.container)
@ -210,10 +209,9 @@ impl ContextMenu {
.with_children(self.items.iter().enumerate().map(|(ix, item)| { .with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item { match item {
ContextMenuItem::Item { action, .. } => { ContextMenuItem::Item { action, .. } => {
let style = style.item.style_for( let style = style
&Default::default(), .item
Some(ix) == self.selected_index, .style_for(Default::default(), Some(ix) == self.selected_index);
);
KeystrokeLabel::new( KeystrokeLabel::new(
action.boxed_clone(), action.boxed_clone(),
style.keystroke.container, style.keystroke.container,

View file

@ -592,11 +592,11 @@ impl ContextMenu {
&self, &self,
cursor_position: DisplayPoint, cursor_position: DisplayPoint,
style: EditorStyle, style: EditorStyle,
cx: &AppContext, cx: &mut RenderContext<Editor>,
) -> (DisplayPoint, ElementBox) { ) -> (DisplayPoint, ElementBox) {
match self { match self {
ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)),
ContextMenu::CodeActions(menu) => menu.render(cursor_position, style), ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
} }
} }
} }
@ -633,14 +633,18 @@ impl CompletionsMenu {
!self.matches.is_empty() !self.matches.is_empty()
} }
fn render(&self, style: EditorStyle, _: &AppContext) -> ElementBox { fn render(&self, style: EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
enum CompletionTag {} enum CompletionTag {}
let completions = self.completions.clone(); let completions = self.completions.clone();
let matches = self.matches.clone(); let matches = self.matches.clone();
let selected_item = self.selected_item; let selected_item = self.selected_item;
let container_style = style.autocomplete.container; let container_style = style.autocomplete.container;
UniformList::new(self.list.clone(), matches.len(), move |range, items, cx| { UniformList::new(
self.list.clone(),
matches.len(),
cx,
move |_, range, items, cx| {
let start_ix = range.start; let start_ix = range.start;
for (ix, mat) in matches[range].iter().enumerate() { for (ix, mat) in matches[range].iter().enumerate() {
let completion = &completions[mat.candidate_id]; let completion = &completions[mat.candidate_id];
@ -663,7 +667,10 @@ impl CompletionsMenu {
.with_highlights(combine_syntax_and_fuzzy_match_highlights( .with_highlights(combine_syntax_and_fuzzy_match_highlights(
&completion.label.text, &completion.label.text,
style.text.color.into(), style.text.color.into(),
styled_runs_for_code_label(&completion.label, &style.syntax), styled_runs_for_code_label(
&completion.label,
&style.syntax,
),
&mat.positions, &mat.positions,
)) ))
.contained() .contained()
@ -680,7 +687,8 @@ impl CompletionsMenu {
.boxed(), .boxed(),
); );
} }
}) },
)
.with_width_from_item( .with_width_from_item(
self.matches self.matches
.iter() .iter()
@ -772,14 +780,18 @@ impl CodeActionsMenu {
&self, &self,
mut cursor_position: DisplayPoint, mut cursor_position: DisplayPoint,
style: EditorStyle, style: EditorStyle,
cx: &mut RenderContext<Editor>,
) -> (DisplayPoint, ElementBox) { ) -> (DisplayPoint, ElementBox) {
enum ActionTag {} enum ActionTag {}
let container_style = style.autocomplete.container; let container_style = style.autocomplete.container;
let actions = self.actions.clone(); let actions = self.actions.clone();
let selected_item = self.selected_item; let selected_item = self.selected_item;
let element = let element = UniformList::new(
UniformList::new(self.list.clone(), actions.len(), move |range, items, cx| { self.list.clone(),
actions.len(),
cx,
move |_, range, items, cx| {
let start_ix = range.start; let start_ix = range.start;
for (ix, action) in actions[range].iter().enumerate() { for (ix, action) in actions[range].iter().enumerate() {
let item_ix = start_ix + ix; let item_ix = start_ix + ix;
@ -808,7 +820,8 @@ impl CodeActionsMenu {
.boxed(), .boxed(),
); );
} }
}) },
)
.with_width_from_item( .with_width_from_item(
self.actions self.actions
.iter() .iter()
@ -2578,7 +2591,7 @@ impl Editor {
pub fn render_code_actions_indicator( pub fn render_code_actions_indicator(
&self, &self,
style: &EditorStyle, style: &EditorStyle,
cx: &mut ViewContext<Self>, cx: &mut RenderContext<Self>,
) -> Option<ElementBox> { ) -> Option<ElementBox> {
if self.available_code_actions.is_some() { if self.available_code_actions.is_some() {
enum Tag {} enum Tag {}
@ -2612,7 +2625,7 @@ impl Editor {
&self, &self,
cursor_position: DisplayPoint, cursor_position: DisplayPoint,
style: EditorStyle, style: EditorStyle,
cx: &AppContext, cx: &mut RenderContext<Editor>,
) -> Option<(DisplayPoint, ElementBox)> { ) -> Option<(DisplayPoint, ElementBox)> {
self.context_menu self.context_menu
.as_ref() .as_ref()

View file

@ -18,8 +18,9 @@ use gpui::{
json::{self, ToJson}, json::{self, ToJson},
platform::CursorStyle, platform::CursorStyle,
text_layout::{self, Line, RunStyle, TextLayoutCache}, text_layout::{self, Line, RunStyle, TextLayoutCache},
AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext, AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext,
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, LayoutContext, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext,
WeakViewHandle,
}; };
use json::json; use json::json;
use language::{Bias, DiagnosticSeverity}; use language::{Bias, DiagnosticSeverity};
@ -330,7 +331,10 @@ impl EditorElement {
let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
cx.scene.push_layer(Some(bounds)); cx.scene.push_layer(Some(bounds));
cx.scene.push_cursor_style(bounds, CursorStyle::IBeam); cx.scene.push_cursor_region(CursorRegion {
bounds,
style: CursorStyle::IBeam,
});
for (range, color) in &layout.highlighted_ranges { for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range( self.paint_highlighted_range(
@ -1020,8 +1024,6 @@ impl Element for EditorElement {
max_row.saturating_sub(1) as f32, max_row.saturating_sub(1) as f32,
); );
let mut context_menu = None;
let mut code_actions_indicator = None;
self.update_view(cx.app, |view, cx| { self.update_view(cx.app, |view, cx| {
let clamped = view.clamp_scroll_left(scroll_max.x()); let clamped = view.clamp_scroll_left(scroll_max.x());
let autoscrolled; let autoscrolled;
@ -1041,7 +1043,11 @@ impl Element for EditorElement {
if clamped || autoscrolled { if clamped || autoscrolled {
snapshot = view.snapshot(cx); snapshot = view.snapshot(cx);
} }
});
let mut context_menu = None;
let mut code_actions_indicator = None;
cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
let newest_selection_head = view let newest_selection_head = view
.selections .selections
.newest::<usize>(cx) .newest::<usize>(cx)

View file

@ -1,7 +1,7 @@
use fuzzy::PathMatch; use fuzzy::PathMatch;
use gpui::{ use gpui::{
actions, elements::*, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, actions, elements::*, AppContext, Entity, ModelHandle, MouseState, MutableAppContext,
View, ViewContext, ViewHandle, RenderContext, Task, View, ViewContext, ViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{Project, ProjectPath, WorktreeId}; use project::{Project, ProjectPath, WorktreeId};
@ -226,7 +226,7 @@ impl PickerDelegate for FileFinder {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &MouseState, mouse_state: MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> ElementBox {

View file

@ -7,7 +7,8 @@ use crate::{
platform::{self, Platform, PromptLevel, WindowOptions}, platform::{self, Platform, PromptLevel, WindowOptions},
presenter::Presenter, presenter::Presenter,
util::post_inc, util::post_inc,
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache, AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, PathPromptOptions,
TextLayoutCache,
}; };
pub use action::*; pub use action::*;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
@ -126,26 +127,6 @@ pub trait UpdateView {
T: View; T: View;
} }
pub trait ElementStateContext: DerefMut<Target = MutableAppContext> {
fn current_view_id(&self) -> usize;
fn element_state<Tag: 'static, T: 'static + Default>(
&mut self,
element_id: usize,
) -> ElementStateHandle<T> {
let id = ElementStateId {
view_id: self.current_view_id(),
element_id,
tag: TypeId::of::<Tag>(),
};
self.cx
.element_states
.entry(id)
.or_insert_with(|| Box::new(T::default()));
ElementStateHandle::new(id, self.frame_count, &self.cx.ref_counts)
}
}
pub struct Menu<'a> { pub struct Menu<'a> {
pub name: &'a str, pub name: &'a str,
pub items: Vec<MenuItem<'a>>, pub items: Vec<MenuItem<'a>>,
@ -467,6 +448,27 @@ impl TestAppContext {
result result
} }
pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
where
F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
V: View,
{
handle.update(&mut *self.cx.borrow_mut(), |view, cx| {
let mut render_cx = RenderContext {
app: cx,
window_id: handle.window_id(),
view_id: handle.id(),
view_type: PhantomData,
titlebar_height: 0.,
hovered_region_ids: Default::default(),
clicked_region_id: None,
right_clicked_region_id: None,
refreshing: false,
};
f(view, &mut render_cx)
})
}
pub fn to_async(&self) -> AsyncAppContext { pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext(self.cx.clone()) AsyncAppContext(self.cx.clone())
} }
@ -1040,19 +1042,15 @@ impl MutableAppContext {
.map_or(false, |window| window.is_active) .map_or(false, |window| window.is_active)
} }
pub fn render_view( pub fn render_view(&mut self, params: RenderParams) -> Result<ElementBox> {
&mut self, let window_id = params.window_id;
window_id: usize, let view_id = params.view_id;
view_id: usize,
titlebar_height: f32,
refreshing: bool,
) -> Result<ElementBox> {
let mut view = self let mut view = self
.cx .cx
.views .views
.remove(&(window_id, view_id)) .remove(&(params.window_id, params.view_id))
.ok_or(anyhow!("view not found"))?; .ok_or(anyhow!("view not found"))?;
let element = view.render(window_id, view_id, titlebar_height, refreshing, self); let element = view.render(params, self);
self.cx.views.insert((window_id, view_id), view); self.cx.views.insert((window_id, view_id), view);
Ok(element) Ok(element)
} }
@ -1079,7 +1077,15 @@ impl MutableAppContext {
.map(|view_id| { .map(|view_id| {
( (
view_id, view_id,
self.render_view(window_id, view_id, titlebar_height, false) self.render_view(RenderParams {
window_id,
view_id,
titlebar_height,
hovered_region_ids: Default::default(),
clicked_region_id: None,
right_clicked_region_id: None,
refreshing: false,
})
.unwrap(), .unwrap(),
) )
}) })
@ -1366,7 +1372,10 @@ impl MutableAppContext {
.unwrap() .unwrap()
.0 .0
.clone(); .clone();
let dispatch_path = presenter.borrow().dispatch_path_from(view_id); let mut dispatch_path = Vec::new();
presenter
.borrow()
.compute_dispatch_path_from(view_id, &mut dispatch_path);
for view_id in dispatch_path { for view_id in dispatch_path {
if let Some(view) = self.views.get(&(window_id, view_id)) { if let Some(view) = self.views.get(&(window_id, view_id)) {
let view_type = view.as_any().type_id(); let view_type = view.as_any().type_id();
@ -1443,7 +1452,10 @@ impl MutableAppContext {
.unwrap() .unwrap()
.0 .0
.clone(); .clone();
let dispatch_path = presenter.borrow().dispatch_path_from(view_id); let mut dispatch_path = Vec::new();
presenter
.borrow()
.compute_dispatch_path_from(view_id, &mut dispatch_path);
self.dispatch_action_any(window_id, &dispatch_path, action); self.dispatch_action_any(window_id, &dispatch_path, action);
} }
@ -1775,23 +1787,6 @@ impl MutableAppContext {
) )
} }
pub fn build_render_context<V: View>(
&mut self,
window_id: usize,
view_id: usize,
titlebar_height: f32,
refreshing: bool,
) -> RenderContext<V> {
RenderContext {
app: self,
titlebar_height,
refreshing,
window_id,
view_id,
view_type: PhantomData,
}
}
pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T> pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
where where
T: View, T: View,
@ -2917,14 +2912,7 @@ pub trait AnyView {
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>; ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>;
fn ui_name(&self) -> &'static str; fn ui_name(&self) -> &'static str;
fn render<'a>( fn render<'a>(&mut self, params: RenderParams, cx: &mut MutableAppContext) -> ElementBox;
&mut self,
window_id: usize,
view_id: usize,
titlebar_height: f32,
refreshing: bool,
cx: &mut MutableAppContext,
) -> ElementBox;
fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize); fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize); fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
fn keymap_context(&self, cx: &AppContext) -> keymap::Context; fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
@ -2958,25 +2946,8 @@ where
T::ui_name() T::ui_name()
} }
fn render<'a>( fn render<'a>(&mut self, params: RenderParams, cx: &mut MutableAppContext) -> ElementBox {
&mut self, View::render(self, &mut RenderContext::new(params, cx))
window_id: usize,
view_id: usize,
titlebar_height: f32,
refreshing: bool,
cx: &mut MutableAppContext,
) -> ElementBox {
View::render(
self,
&mut RenderContext {
window_id,
view_id,
app: cx,
view_type: PhantomData::<T>,
titlebar_height,
refreshing,
},
)
} }
fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) { fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
@ -3458,23 +3429,86 @@ impl<'a, T: View> ViewContext<'a, T> {
} }
} }
pub struct RenderParams {
pub window_id: usize,
pub view_id: usize,
pub titlebar_height: f32,
pub hovered_region_ids: HashSet<MouseRegionId>,
pub clicked_region_id: Option<MouseRegionId>,
pub right_clicked_region_id: Option<MouseRegionId>,
pub refreshing: bool,
}
pub struct RenderContext<'a, T: View> { pub struct RenderContext<'a, T: View> {
pub(crate) window_id: usize,
pub(crate) view_id: usize,
pub(crate) view_type: PhantomData<T>,
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
pub(crate) clicked_region_id: Option<MouseRegionId>,
pub(crate) right_clicked_region_id: Option<MouseRegionId>,
pub app: &'a mut MutableAppContext, pub app: &'a mut MutableAppContext,
pub titlebar_height: f32, pub titlebar_height: f32,
pub refreshing: bool, pub refreshing: bool,
window_id: usize,
view_id: usize,
view_type: PhantomData<T>,
} }
impl<'a, T: View> RenderContext<'a, T> { #[derive(Clone, Copy, Default)]
pub fn handle(&self) -> WeakViewHandle<T> { pub struct MouseState {
pub hovered: bool,
pub clicked: bool,
pub right_clicked: bool,
}
impl<'a, V: View> RenderContext<'a, V> {
fn new(params: RenderParams, app: &'a mut MutableAppContext) -> Self {
Self {
app,
window_id: params.window_id,
view_id: params.view_id,
view_type: PhantomData,
titlebar_height: params.titlebar_height,
hovered_region_ids: params.hovered_region_ids.clone(),
clicked_region_id: params.clicked_region_id,
right_clicked_region_id: params.right_clicked_region_id,
refreshing: params.refreshing,
}
}
pub fn handle(&self) -> WeakViewHandle<V> {
WeakViewHandle::new(self.window_id, self.view_id) WeakViewHandle::new(self.window_id, self.view_id)
} }
pub fn view_id(&self) -> usize { pub fn view_id(&self) -> usize {
self.view_id self.view_id
} }
pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
let region_id = MouseRegionId {
view_id: self.view_id,
tag: TypeId::of::<Tag>(),
region_id,
};
MouseState {
hovered: self.hovered_region_ids.contains(&region_id),
clicked: self.clicked_region_id == Some(region_id),
right_clicked: self.right_clicked_region_id == Some(region_id),
}
}
pub fn element_state<Tag: 'static, T: 'static + Default>(
&mut self,
element_id: usize,
) -> ElementStateHandle<T> {
let id = ElementStateId {
view_id: self.view_id(),
element_id,
tag: TypeId::of::<Tag>(),
};
self.cx
.element_states
.entry(id)
.or_insert_with(|| Box::new(T::default()));
ElementStateHandle::new(id, self.frame_count, &self.cx.ref_counts)
}
} }
impl AsRef<AppContext> for &AppContext { impl AsRef<AppContext> for &AppContext {
@ -3519,12 +3553,6 @@ impl<V: View> ReadView for RenderContext<'_, V> {
} }
} }
impl<V: View> ElementStateContext for RenderContext<'_, V> {
fn current_view_id(&self) -> usize {
self.view_id
}
}
impl<M> AsRef<AppContext> for ViewContext<'_, M> { impl<M> AsRef<AppContext> for ViewContext<'_, M> {
fn as_ref(&self) -> &AppContext { fn as_ref(&self) -> &AppContext {
&self.app.cx &self.app.cx
@ -3584,6 +3612,16 @@ impl<V> UpgradeViewHandle for ViewContext<'_, V> {
} }
} }
impl<V: View> UpgradeViewHandle for RenderContext<'_, V> {
fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
self.cx.upgrade_view_handle(handle)
}
fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
self.cx.upgrade_any_view_handle(handle)
}
}
impl<V: View> UpdateModel for ViewContext<'_, V> { impl<V: View> UpdateModel for ViewContext<'_, V> {
fn update_model<T: Entity, O>( fn update_model<T: Entity, O>(
&mut self, &mut self,
@ -3613,12 +3651,6 @@ impl<V: View> UpdateView for ViewContext<'_, V> {
} }
} }
impl<V: View> ElementStateContext for ViewContext<'_, V> {
fn current_view_id(&self) -> usize {
self.view_id
}
}
pub trait Handle<T> { pub trait Handle<T> {
type Weak: 'static; type Weak: 'static;
fn id(&self) -> usize; fn id(&self) -> usize;

View file

@ -7,7 +7,7 @@ use crate::{
}, },
json::ToJson, json::ToJson,
platform::CursorStyle, platform::CursorStyle,
scene::{self, Border, Quad}, scene::{self, Border, CursorRegion, Quad},
Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
}; };
use serde::Deserialize; use serde::Deserialize;
@ -213,7 +213,10 @@ impl Element for Container {
} }
if let Some(style) = self.style.cursor { if let Some(style) = self.style.cursor {
cx.scene.push_cursor_style(quad_bounds, style); cx.scene.push_cursor_region(CursorRegion {
bounds: quad_bounds,
style,
});
} }
let child_origin = let child_origin =

View file

@ -2,8 +2,8 @@ use std::{any::Any, f32::INFINITY};
use crate::{ use crate::{
json::{self, ToJson, Value}, json::{self, ToJson, Value},
Axis, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event, Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext,
EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, LayoutContext, PaintContext, RenderContext, SizeConstraint, Vector2FExt, View,
}; };
use pathfinder_geometry::{ use pathfinder_geometry::{
rect::RectF, rect::RectF,
@ -40,15 +40,15 @@ impl Flex {
Self::new(Axis::Vertical) Self::new(Axis::Vertical)
} }
pub fn scrollable<Tag, C>( pub fn scrollable<Tag, V>(
mut self, mut self,
element_id: usize, element_id: usize,
scroll_to: Option<usize>, scroll_to: Option<usize>,
cx: &mut C, cx: &mut RenderContext<V>,
) -> Self ) -> Self
where where
Tag: 'static, Tag: 'static,
C: ElementStateContext, V: View,
{ {
let scroll_state = cx.element_state::<Tag, ScrollState>(element_id); let scroll_state = cx.element_state::<Tag, ScrollState>(element_id);
scroll_state.update(cx, |scroll_state, _| scroll_state.scroll_to = scroll_to); scroll_state.update(cx, |scroll_state, _| scroll_state.scroll_to = scroll_to);

View file

@ -5,7 +5,7 @@ use crate::{
}, },
json::json, json::json,
DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext, DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint, RenderContext, SizeConstraint, View, ViewContext,
}; };
use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc}; use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree}; use sum_tree::{Bias, SumTree};
@ -26,7 +26,7 @@ pub enum Orientation {
struct StateInner { struct StateInner {
last_layout_width: Option<f32>, last_layout_width: Option<f32>,
render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> ElementBox>, render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> Option<ElementBox>>,
rendered_range: Range<usize>, rendered_range: Range<usize>,
items: SumTree<ListItem>, items: SumTree<ListItem>,
logical_scroll_top: Option<ListOffset>, logical_scroll_top: Option<ListOffset>,
@ -135,10 +135,13 @@ impl Element for List {
break; break;
} }
let element = state.render_item(scroll_top.item_ix + ix, item, item_constraint, cx); if let Some(element) =
state.render_item(scroll_top.item_ix + ix, item, item_constraint, cx)
{
rendered_height += element.size().y(); rendered_height += element.size().y();
rendered_items.push_back(ListItem::Rendered(element)); rendered_items.push_back(ListItem::Rendered(element));
} }
}
// Prepare to start walking upward from the item at the scroll top. // Prepare to start walking upward from the item at the scroll top.
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
@ -149,9 +152,12 @@ impl Element for List {
while rendered_height < size.y() { while rendered_height < size.y() {
cursor.prev(&()); cursor.prev(&());
if let Some(item) = cursor.item() { if let Some(item) = cursor.item() {
let element = state.render_item(cursor.start().0, item, item_constraint, cx); if let Some(element) =
state.render_item(cursor.start().0, item, item_constraint, cx)
{
rendered_height += element.size().y(); rendered_height += element.size().y();
rendered_items.push_front(ListItem::Rendered(element)); rendered_items.push_front(ListItem::Rendered(element));
}
} else { } else {
break; break;
} }
@ -182,9 +188,12 @@ impl Element for List {
while leading_overdraw < state.overdraw { while leading_overdraw < state.overdraw {
cursor.prev(&()); cursor.prev(&());
if let Some(item) = cursor.item() { if let Some(item) = cursor.item() {
let element = state.render_item(cursor.start().0, item, item_constraint, cx); if let Some(element) =
state.render_item(cursor.start().0, item, item_constraint, cx)
{
leading_overdraw += element.size().y(); leading_overdraw += element.size().y();
rendered_items.push_front(ListItem::Rendered(element)); rendered_items.push_front(ListItem::Rendered(element));
}
} else { } else {
break; break;
} }
@ -330,20 +339,25 @@ impl Element for List {
} }
impl ListState { impl ListState {
pub fn new<F>( pub fn new<F, V>(
element_count: usize, element_count: usize,
orientation: Orientation, orientation: Orientation,
overdraw: f32, overdraw: f32,
render_item: F, cx: &mut ViewContext<V>,
mut render_item: F,
) -> Self ) -> Self
where where
F: 'static + FnMut(usize, &mut LayoutContext) -> ElementBox, V: View,
F: 'static + FnMut(&mut V, usize, &mut RenderContext<V>) -> ElementBox,
{ {
let mut items = SumTree::new(); let mut items = SumTree::new();
items.extend((0..element_count).map(|_| ListItem::Unrendered), &()); items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
let handle = cx.handle();
Self(Rc::new(RefCell::new(StateInner { Self(Rc::new(RefCell::new(StateInner {
last_layout_width: None, last_layout_width: None,
render_item: Box::new(render_item), render_item: Box::new(move |ix, cx| {
Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
}),
rendered_range: 0..0, rendered_range: 0..0,
items, items,
logical_scroll_top: None, logical_scroll_top: None,
@ -414,13 +428,13 @@ impl StateInner {
existing_item: &ListItem, existing_item: &ListItem,
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> ElementRc { ) -> Option<ElementRc> {
if let ListItem::Rendered(element) = existing_item { if let ListItem::Rendered(element) = existing_item {
element.clone() Some(element.clone())
} else { } else {
let mut element = (self.render_item)(ix, cx); let mut element = (self.render_item)(ix, cx)?;
element.layout(constraint, cx); element.layout(constraint, cx);
element.into() Some(element.into())
} }
} }
@ -593,22 +607,26 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::geometry::vector::vec2f; use crate::{elements::Empty, geometry::vector::vec2f, Entity};
use rand::prelude::*; use rand::prelude::*;
use std::env; use std::env;
#[crate::test(self)] #[crate::test(self)]
fn test_layout(cx: &mut crate::MutableAppContext) { fn test_layout(cx: &mut crate::MutableAppContext) {
let mut presenter = cx.build_presenter(0, 0.); let mut presenter = cx.build_presenter(0, 0.);
let (_, view) = cx.add_window(Default::default(), |_| TestView);
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)); let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)])); let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
let state = view.update(cx, |_, cx| {
ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
let elements = elements.clone(); let elements = elements.clone();
move |ix, _| { move |_, ix, _| {
let (id, height) = elements.borrow()[ix]; let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed() TestElement::new(id, height).boxed()
} }
})
}); });
let mut list = List::new(state.clone()); let mut list = List::new(state.clone());
@ -694,6 +712,7 @@ mod tests {
.map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10); .unwrap_or(10);
let (_, view) = cx.add_window(Default::default(), |_| TestView);
let mut presenter = cx.build_presenter(0, 0.); let mut presenter = cx.build_presenter(0, 0.);
let mut next_id = 0; let mut next_id = 0;
let elements = Rc::new(RefCell::new( let elements = Rc::new(RefCell::new(
@ -709,12 +728,15 @@ mod tests {
.choose(&mut rng) .choose(&mut rng)
.unwrap(); .unwrap();
let overdraw = rng.gen_range(1..=100) as f32; let overdraw = rng.gen_range(1..=100) as f32;
let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
let state = view.update(cx, |_, cx| {
ListState::new(elements.borrow().len(), orientation, overdraw, cx, {
let elements = elements.clone(); let elements = elements.clone();
move |ix, _| { move |_, ix, _| {
let (id, height) = elements.borrow()[ix]; let (id, height) = elements.borrow()[ix];
TestElement::new(id, height).boxed() TestElement::new(id, height).boxed()
} }
})
}); });
let mut width = rng.gen_range(0..=2000) as f32 / 2.; let mut width = rng.gen_range(0..=2000) as f32 / 2.;
@ -851,6 +873,22 @@ mod tests {
} }
} }
struct TestView;
impl Entity for TestView {
type Event = ();
}
impl View for TestView {
fn ui_name() -> &'static str {
"TestView"
}
fn render(&mut self, _: &mut RenderContext<'_, Self>) -> ElementBox {
Empty::new().boxed()
}
}
struct TestElement { struct TestElement {
id: usize, id: usize,
size: Vector2F, size: Vector2F,

View file

@ -1,3 +1,5 @@
use std::{any::TypeId, rc::Rc};
use super::Padding; use super::Padding;
use crate::{ use crate::{
geometry::{ geometry::{
@ -5,49 +7,42 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
platform::CursorStyle, platform::CursorStyle,
DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event, scene::CursorRegion,
EventContext, LayoutContext, PaintContext, SizeConstraint, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion, MouseState,
PaintContext, RenderContext, SizeConstraint, View,
}; };
use serde_json::json; use serde_json::json;
pub struct MouseEventHandler { pub struct MouseEventHandler {
state: ElementStateHandle<MouseState>,
child: ElementBox, child: ElementBox,
tag: TypeId,
id: usize,
cursor_style: Option<CursorStyle>, cursor_style: Option<CursorStyle>,
mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>, mouse_down_handler: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>, click_handler: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
drag_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>, right_mouse_down_handler: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
right_mouse_down_handler: Option<Box<dyn FnMut(Vector2F, &mut EventContext)>>, right_click_handler: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
right_click_handler: Option<Box<dyn FnMut(Vector2F, usize, &mut EventContext)>>, drag_handler: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
padding: Padding, padding: Padding,
} }
#[derive(Default, Debug)]
pub struct MouseState {
pub hovered: bool,
pub clicked: bool,
pub right_clicked: bool,
prev_drag_position: Option<Vector2F>,
}
impl MouseEventHandler { impl MouseEventHandler {
pub fn new<Tag, C, F>(id: usize, cx: &mut C, render_child: F) -> Self pub fn new<Tag, V, F>(id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
where where
Tag: 'static, Tag: 'static,
C: ElementStateContext, V: View,
F: FnOnce(&MouseState, &mut C) -> ElementBox, F: FnOnce(MouseState, &mut RenderContext<V>) -> ElementBox,
{ {
let state_handle = cx.element_state::<Tag, _>(id);
let child = state_handle.update(cx, |state, cx| render_child(state, cx));
Self { Self {
state: state_handle, id,
child, tag: TypeId::of::<Tag>(),
child: render_child(cx.mouse_state::<Tag>(id), cx),
cursor_style: None, cursor_style: None,
mouse_down_handler: None, mouse_down_handler: None,
click_handler: None, click_handler: None,
drag_handler: None,
right_mouse_down_handler: None, right_mouse_down_handler: None,
right_click_handler: None, right_click_handler: None,
drag_handler: None,
padding: Default::default(), padding: Default::default(),
} }
} }
@ -59,38 +54,38 @@ impl MouseEventHandler {
pub fn on_mouse_down( pub fn on_mouse_down(
mut self, mut self,
handler: impl FnMut(Vector2F, &mut EventContext) + 'static, handler: impl Fn(Vector2F, &mut EventContext) + 'static,
) -> Self { ) -> Self {
self.mouse_down_handler = Some(Box::new(handler)); self.mouse_down_handler = Some(Rc::new(handler));
self self
} }
pub fn on_click( pub fn on_click(
mut self, mut self,
handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static, handler: impl Fn(Vector2F, usize, &mut EventContext) + 'static,
) -> Self { ) -> Self {
self.click_handler = Some(Box::new(handler)); self.click_handler = Some(Rc::new(handler));
self
}
pub fn on_drag(mut self, handler: impl FnMut(Vector2F, &mut EventContext) + 'static) -> Self {
self.drag_handler = Some(Box::new(handler));
self self
} }
pub fn on_right_mouse_down( pub fn on_right_mouse_down(
mut self, mut self,
handler: impl FnMut(Vector2F, &mut EventContext) + 'static, handler: impl Fn(Vector2F, &mut EventContext) + 'static,
) -> Self { ) -> Self {
self.right_mouse_down_handler = Some(Box::new(handler)); self.right_mouse_down_handler = Some(Rc::new(handler));
self self
} }
pub fn on_right_click( pub fn on_right_click(
mut self, mut self,
handler: impl FnMut(Vector2F, usize, &mut EventContext) + 'static, handler: impl Fn(Vector2F, usize, &mut EventContext) + 'static,
) -> Self { ) -> Self {
self.right_click_handler = Some(Box::new(handler)); self.right_click_handler = Some(Rc::new(handler));
self
}
pub fn on_drag(mut self, handler: impl Fn(Vector2F, &mut EventContext) + 'static) -> Self {
self.drag_handler = Some(Rc::new(handler));
self self
} }
@ -127,10 +122,26 @@ impl Element for MouseEventHandler {
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
cx: &mut PaintContext, cx: &mut PaintContext,
) -> Self::PaintState { ) -> Self::PaintState {
if let Some(cursor_style) = self.cursor_style { if let Some(style) = self.cursor_style {
cx.scene cx.scene.push_cursor_region(CursorRegion {
.push_cursor_style(self.hit_bounds(bounds), cursor_style); bounds: self.hit_bounds(bounds),
style,
});
} }
cx.scene.push_mouse_region(MouseRegion {
view_id: cx.current_view_id(),
tag: self.tag,
region_id: self.id,
bounds: self.hit_bounds(bounds),
hover: None,
click: self.click_handler.clone(),
mouse_down: self.mouse_down_handler.clone(),
right_click: self.right_click_handler.clone(),
right_mouse_down: self.right_mouse_down_handler.clone(),
drag: self.drag_handler.clone(),
});
self.child.paint(bounds.origin(), visible_bounds, cx); self.child.paint(bounds.origin(), visible_bounds, cx);
} }
@ -138,113 +149,12 @@ impl Element for MouseEventHandler {
&mut self, &mut self,
event: &Event, event: &Event,
_: RectF, _: RectF,
visible_bounds: RectF, _: RectF,
_: &mut Self::LayoutState, _: &mut Self::LayoutState,
_: &mut Self::PaintState, _: &mut Self::PaintState,
cx: &mut EventContext, cx: &mut EventContext,
) -> bool { ) -> bool {
let hit_bounds = self.hit_bounds(visible_bounds); self.child.dispatch_event(event, cx)
let mouse_down_handler = self.mouse_down_handler.as_mut();
let click_handler = self.click_handler.as_mut();
let drag_handler = self.drag_handler.as_mut();
let right_mouse_down_handler = self.right_mouse_down_handler.as_mut();
let right_click_handler = self.right_click_handler.as_mut();
let handled_in_child = self.child.dispatch_event(event, cx);
self.state.update(cx, |state, cx| match event {
Event::MouseMoved {
position,
left_mouse_down,
} => {
if !left_mouse_down {
let mouse_in = hit_bounds.contains_point(*position);
if state.hovered != mouse_in {
state.hovered = mouse_in;
cx.notify();
return true;
}
}
handled_in_child
}
Event::LeftMouseDown { position, .. } => {
if !handled_in_child && hit_bounds.contains_point(*position) {
state.clicked = true;
state.prev_drag_position = Some(*position);
cx.notify();
if let Some(handler) = mouse_down_handler {
handler(*position, cx);
}
true
} else {
handled_in_child
}
}
Event::LeftMouseUp {
position,
click_count,
..
} => {
state.prev_drag_position = None;
if !handled_in_child && state.clicked {
state.clicked = false;
cx.notify();
if let Some(handler) = click_handler {
if hit_bounds.contains_point(*position) {
handler(*position, *click_count, cx);
}
}
true
} else {
handled_in_child
}
}
Event::LeftMouseDragged { position, .. } => {
if !handled_in_child && state.clicked {
let prev_drag_position = state.prev_drag_position.replace(*position);
if let Some((handler, prev_position)) = drag_handler.zip(prev_drag_position) {
let delta = *position - prev_position;
if !delta.is_zero() {
(handler)(delta, cx);
}
}
true
} else {
handled_in_child
}
}
Event::RightMouseDown { position, .. } => {
if !handled_in_child && hit_bounds.contains_point(*position) {
state.right_clicked = true;
cx.notify();
if let Some(handler) = right_mouse_down_handler {
handler(*position, cx);
}
true
} else {
handled_in_child
}
}
Event::RightMouseUp {
position,
click_count,
..
} => {
if !handled_in_child && state.right_clicked {
state.right_clicked = false;
cx.notify();
if let Some(handler) = right_click_handler {
if hit_bounds.contains_point(*position) {
handler(*position, *click_count, cx);
}
}
true
} else {
handled_in_child
}
}
_ => handled_in_child,
})
} }
fn debug( fn debug(

View file

@ -5,7 +5,7 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::{self, json}, json::{self, json},
ElementBox, ElementBox, RenderContext, View,
}; };
use json::ToJson; use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -41,27 +41,37 @@ pub struct LayoutState {
items: Vec<ElementBox>, items: Vec<ElementBox>,
} }
pub struct UniformList<F> pub struct UniformList {
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
state: UniformListState, state: UniformListState,
item_count: usize, item_count: usize,
append_items: F, append_items: Box<dyn Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext)>,
padding_top: f32, padding_top: f32,
padding_bottom: f32, padding_bottom: f32,
get_width_from_item: Option<usize>, get_width_from_item: Option<usize>,
} }
impl<F> UniformList<F> impl UniformList {
pub fn new<F, V>(
state: UniformListState,
item_count: usize,
cx: &mut RenderContext<V>,
append_items: F,
) -> Self
where where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext), V: View,
F: 'static + Fn(&mut V, Range<usize>, &mut Vec<ElementBox>, &mut RenderContext<V>),
{ {
pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self { let handle = cx.handle();
Self { Self {
state, state,
item_count, item_count,
append_items, append_items: Box::new(move |range, items, cx| {
if let Some(handle) = handle.upgrade(cx) {
cx.render(&handle, |view, cx| {
append_items(view, range, items, cx);
});
}
}),
padding_top: 0., padding_top: 0.,
padding_bottom: 0., padding_bottom: 0.,
get_width_from_item: None, get_width_from_item: None,
@ -144,10 +154,7 @@ where
} }
} }
impl<F> Element for UniformList<F> impl Element for UniformList {
where
F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext),
{
type LayoutState = LayoutState; type LayoutState = LayoutState;
type PaintState = (); type PaintState = ();
@ -162,8 +169,7 @@ where
); );
} }
if self.item_count == 0 { let no_items = (
return (
constraint.min, constraint.min,
LayoutState { LayoutState {
item_height: 0., item_height: 0.,
@ -171,24 +177,32 @@ where
items: Default::default(), items: Default::default(),
}, },
); );
if self.item_count == 0 {
return no_items;
} }
let mut items = Vec::new(); let mut items = Vec::new();
let mut size = constraint.max; let mut size = constraint.max;
let mut item_size; let mut item_size;
let sample_item_ix; let sample_item_ix;
let mut sample_item; let sample_item;
if let Some(sample_ix) = self.get_width_from_item { if let Some(sample_ix) = self.get_width_from_item {
(self.append_items)(sample_ix..sample_ix + 1, &mut items, cx); (self.append_items)(sample_ix..sample_ix + 1, &mut items, cx);
sample_item_ix = sample_ix; sample_item_ix = sample_ix;
sample_item = items.pop().unwrap();
item_size = sample_item.layout(constraint, cx); if let Some(mut item) = items.pop() {
item_size = item.layout(constraint, cx);
size.set_x(item_size.x()); size.set_x(item_size.x());
sample_item = item;
} else {
return no_items;
}
} else { } else {
(self.append_items)(0..1, &mut items, cx); (self.append_items)(0..1, &mut items, cx);
sample_item_ix = 0; sample_item_ix = 0;
sample_item = items.pop().unwrap(); if let Some(mut item) = items.pop() {
item_size = sample_item.layout( item_size = item.layout(
SizeConstraint::new( SizeConstraint::new(
vec2f(constraint.max.x(), 0.0), vec2f(constraint.max.x(), 0.0),
vec2f(constraint.max.x(), f32::INFINITY), vec2f(constraint.max.x(), f32::INFINITY),
@ -196,6 +210,10 @@ where
cx, cx,
); );
item_size.set_x(size.x()); item_size.set_x(size.x());
sample_item = item
} else {
return no_items;
}
} }
let item_constraint = SizeConstraint { let item_constraint = SizeConstraint {

View file

@ -16,7 +16,7 @@ pub mod fonts;
pub mod geometry; pub mod geometry;
mod presenter; mod presenter;
mod scene; mod scene;
pub use scene::{Border, Quad, Scene}; pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene};
pub mod text_layout; pub mod text_layout;
pub use text_layout::TextLayoutCache; pub use text_layout::TextLayoutCache;
mod util; mod util;

View file

@ -6,16 +6,19 @@ use crate::{
json::{self, ToJson}, json::{self, ToJson},
keymap::Keystroke, keymap::Keystroke,
platform::{CursorStyle, Event}, platform::{CursorStyle, Event},
scene::CursorRegion,
text_layout::TextLayoutCache, text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
ElementStateContext, Entity, FontSystem, ModelHandle, ReadModel, ReadView, Scene, FontSystem, ModelHandle, MouseRegion, MouseRegionId, ReadModel, ReadView, RenderContext,
UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle,
WeakViewHandle,
}; };
use pathfinder_geometry::vector::{vec2f, Vector2F}; use pathfinder_geometry::vector::{vec2f, Vector2F};
use serde_json::json; use serde_json::json;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
marker::PhantomData,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
sync::Arc, sync::Arc,
}; };
@ -24,11 +27,16 @@ pub struct Presenter {
window_id: usize, window_id: usize,
pub(crate) rendered_views: HashMap<usize, ElementBox>, pub(crate) rendered_views: HashMap<usize, ElementBox>,
parents: HashMap<usize, usize>, parents: HashMap<usize, usize>,
cursor_styles: Vec<(RectF, CursorStyle)>, cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<MouseRegion>,
font_cache: Arc<FontCache>, font_cache: Arc<FontCache>,
text_layout_cache: TextLayoutCache, text_layout_cache: TextLayoutCache,
asset_cache: Arc<AssetCache>, asset_cache: Arc<AssetCache>,
last_mouse_moved_event: Option<Event>, last_mouse_moved_event: Option<Event>,
hovered_region_ids: HashSet<MouseRegionId>,
clicked_region: Option<MouseRegion>,
right_clicked_region: Option<MouseRegion>,
prev_drag_position: Option<Vector2F>,
titlebar_height: f32, titlebar_height: f32,
} }
@ -45,32 +53,35 @@ impl Presenter {
window_id, window_id,
rendered_views: cx.render_views(window_id, titlebar_height), rendered_views: cx.render_views(window_id, titlebar_height),
parents: HashMap::new(), parents: HashMap::new(),
cursor_styles: Default::default(), cursor_regions: Default::default(),
mouse_regions: Default::default(),
font_cache, font_cache,
text_layout_cache, text_layout_cache,
asset_cache, asset_cache,
last_mouse_moved_event: None, last_mouse_moved_event: None,
hovered_region_ids: Default::default(),
clicked_region: None,
right_clicked_region: None,
prev_drag_position: None,
titlebar_height, titlebar_height,
} }
} }
pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> { pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
let mut path = Vec::new();
if let Some(view_id) = app.focused_view_id(self.window_id) { if let Some(view_id) = app.focused_view_id(self.window_id) {
self.dispatch_path_from(view_id) self.compute_dispatch_path_from(view_id, &mut path)
} else {
Vec::new()
} }
path
} }
pub(crate) fn dispatch_path_from(&self, mut view_id: usize) -> Vec<usize> { pub(crate) fn compute_dispatch_path_from(&self, mut view_id: usize, path: &mut Vec<usize>) {
let mut path = Vec::new();
path.push(view_id); path.push(view_id);
while let Some(parent_id) = self.parents.get(&view_id).copied() { while let Some(parent_id) = self.parents.get(&view_id).copied() {
path.push(parent_id); path.push(parent_id);
view_id = parent_id; view_id = parent_id;
} }
path.reverse(); path.reverse();
path
} }
pub fn invalidate( pub fn invalidate(
@ -87,7 +98,18 @@ impl Presenter {
for view_id in &invalidation.updated { for view_id in &invalidation.updated {
self.rendered_views.insert( self.rendered_views.insert(
*view_id, *view_id,
cx.render_view(self.window_id, *view_id, self.titlebar_height, false) cx.render_view(RenderParams {
window_id: self.window_id,
view_id: *view_id,
titlebar_height: self.titlebar_height,
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
right_clicked_region_id: self
.right_clicked_region
.as_ref()
.map(MouseRegion::id),
refreshing: false,
})
.unwrap(), .unwrap(),
); );
} }
@ -98,7 +120,18 @@ impl Presenter {
for (view_id, view) in &mut self.rendered_views { for (view_id, view) in &mut self.rendered_views {
if !invalidation.updated.contains(view_id) { if !invalidation.updated.contains(view_id) {
*view = cx *view = cx
.render_view(self.window_id, *view_id, self.titlebar_height, true) .render_view(RenderParams {
window_id: self.window_id,
view_id: *view_id,
titlebar_height: self.titlebar_height,
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
right_clicked_region_id: self
.right_clicked_region
.as_ref()
.map(MouseRegion::id),
refreshing: true,
})
.unwrap(); .unwrap();
} }
} }
@ -122,7 +155,8 @@ impl Presenter {
RectF::new(Vector2F::zero(), window_size), RectF::new(Vector2F::zero(), window_size),
); );
self.text_layout_cache.finish_frame(); self.text_layout_cache.finish_frame();
self.cursor_styles = scene.cursor_styles(); self.cursor_regions = scene.cursor_regions();
self.mouse_regions = scene.mouse_regions();
if cx.window_is_active(self.window_id) { if cx.window_is_active(self.window_id) {
if let Some(event) = self.last_mouse_moved_event.clone() { if let Some(event) = self.last_mouse_moved_event.clone() {
@ -153,13 +187,17 @@ impl Presenter {
window_id: self.window_id, window_id: self.window_id,
rendered_views: &mut self.rendered_views, rendered_views: &mut self.rendered_views,
parents: &mut self.parents, parents: &mut self.parents,
refreshing,
window_size,
font_cache: &self.font_cache, font_cache: &self.font_cache,
font_system: cx.platform().fonts(), font_system: cx.platform().fonts(),
text_layout_cache: &self.text_layout_cache, text_layout_cache: &self.text_layout_cache,
asset_cache: &self.asset_cache, asset_cache: &self.asset_cache,
view_stack: Vec::new(), view_stack: Vec::new(),
refreshing,
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id),
right_clicked_region_id: self.right_clicked_region.as_ref().map(MouseRegion::id),
titlebar_height: self.titlebar_height,
window_size,
app: cx, app: cx,
} }
} }
@ -174,13 +212,69 @@ impl Presenter {
font_cache: &self.font_cache, font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache, text_layout_cache: &self.text_layout_cache,
rendered_views: &mut self.rendered_views, rendered_views: &mut self.rendered_views,
view_stack: Vec::new(),
app: cx, app: cx,
} }
} }
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) { pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) { if let Some(root_view_id) = cx.root_view_id(self.window_id) {
let mut invalidated_views = Vec::new();
let mut hovered_regions = Vec::new();
let mut unhovered_regions = Vec::new();
let mut mouse_down_region = None;
let mut clicked_region = None;
let mut right_mouse_down_region = None;
let mut right_clicked_region = None;
let mut dragged_region = None;
match event { match event {
Event::LeftMouseDown { position, .. } => {
for region in self.mouse_regions.iter().rev() {
if region.bounds.contains_point(position) {
invalidated_views.push(region.view_id);
mouse_down_region = Some((region.clone(), position));
self.clicked_region = Some(region.clone());
self.prev_drag_position = Some(position);
break;
}
}
}
Event::LeftMouseUp {
position,
click_count,
..
} => {
self.prev_drag_position.take();
if let Some(region) = self.clicked_region.take() {
invalidated_views.push(region.view_id);
if region.bounds.contains_point(position) {
clicked_region = Some((region, position, click_count));
}
}
}
Event::RightMouseDown { position, .. } => {
for region in self.mouse_regions.iter().rev() {
if region.bounds.contains_point(position) {
invalidated_views.push(region.view_id);
right_mouse_down_region = Some((region.clone(), position));
self.right_clicked_region = Some(region.clone());
break;
}
}
}
Event::RightMouseUp {
position,
click_count,
..
} => {
if let Some(region) = self.right_clicked_region.take() {
invalidated_views.push(region.view_id);
if region.bounds.contains_point(position) {
right_clicked_region = Some((region, position, click_count));
}
}
}
Event::MouseMoved { Event::MouseMoved {
position, position,
left_mouse_down, left_mouse_down,
@ -189,16 +283,43 @@ impl Presenter {
if !left_mouse_down { if !left_mouse_down {
let mut style_to_assign = CursorStyle::Arrow; let mut style_to_assign = CursorStyle::Arrow;
for (bounds, style) in self.cursor_styles.iter().rev() { for region in self.cursor_regions.iter().rev() {
if bounds.contains_point(position) { if region.bounds.contains_point(position) {
style_to_assign = *style; style_to_assign = region.style;
break; break;
} }
} }
cx.platform().set_cursor_style(style_to_assign); cx.platform().set_cursor_style(style_to_assign);
for region in self.mouse_regions.iter().rev() {
let region_id = region.id();
if region.bounds.contains_point(position) {
if !self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
hovered_regions.push(region.clone());
self.hovered_region_ids.insert(region_id);
}
} else {
if self.hovered_region_ids.contains(&region_id) {
invalidated_views.push(region.view_id);
unhovered_regions.push(region.clone());
self.hovered_region_ids.remove(&region_id);
}
}
}
} }
} }
Event::LeftMouseDragged { position } => { Event::LeftMouseDragged { position } => {
if let Some((clicked_region, prev_drag_position)) = self
.clicked_region
.as_ref()
.zip(self.prev_drag_position.as_mut())
{
dragged_region =
Some((clicked_region.clone(), position - *prev_drag_position));
*prev_drag_position = position;
}
self.last_mouse_moved_event = Some(Event::MouseMoved { self.last_mouse_moved_event = Some(Event::MouseMoved {
position, position,
left_mouse_down: true, left_mouse_down: true,
@ -208,16 +329,88 @@ impl Presenter {
} }
let mut event_cx = self.build_event_context(cx); let mut event_cx = self.build_event_context(cx);
event_cx.dispatch_event(root_view_id, &event); let mut handled = false;
for unhovered_region in unhovered_regions {
if let Some(hover_callback) = unhovered_region.hover {
handled = true;
event_cx.with_current_view(unhovered_region.view_id, |event_cx| {
hover_callback(false, event_cx);
})
}
}
let invalidated_views = event_cx.invalidated_views; for hovered_region in hovered_regions {
if let Some(hover_callback) = hovered_region.hover {
handled = true;
event_cx.with_current_view(hovered_region.view_id, |event_cx| {
hover_callback(true, event_cx);
})
}
}
if let Some((mouse_down_region, position)) = mouse_down_region {
if let Some(mouse_down_callback) = mouse_down_region.mouse_down {
handled = true;
event_cx.with_current_view(mouse_down_region.view_id, |event_cx| {
mouse_down_callback(position, event_cx);
})
}
}
if let Some((clicked_region, position, click_count)) = clicked_region {
if let Some(click_callback) = clicked_region.click {
handled = true;
event_cx.with_current_view(clicked_region.view_id, |event_cx| {
click_callback(position, click_count, event_cx);
})
}
}
if let Some((right_mouse_down_region, position)) = right_mouse_down_region {
if let Some(right_mouse_down_callback) = right_mouse_down_region.right_mouse_down {
handled = true;
event_cx.with_current_view(right_mouse_down_region.view_id, |event_cx| {
right_mouse_down_callback(position, event_cx);
})
}
}
if let Some((right_clicked_region, position, click_count)) = right_clicked_region {
if let Some(right_click_callback) = right_clicked_region.right_click {
handled = true;
event_cx.with_current_view(right_clicked_region.view_id, |event_cx| {
right_click_callback(position, click_count, event_cx);
})
}
}
if let Some((dragged_region, delta)) = dragged_region {
if let Some(drag_callback) = dragged_region.drag {
handled = true;
event_cx.with_current_view(dragged_region.view_id, |event_cx| {
drag_callback(delta, event_cx);
})
}
}
if !handled {
event_cx.dispatch_event(root_view_id, &event);
}
invalidated_views.extend(event_cx.invalidated_views);
let dispatch_directives = event_cx.dispatched_actions; let dispatch_directives = event_cx.dispatched_actions;
for view_id in invalidated_views { for view_id in invalidated_views {
cx.notify_view(self.window_id, view_id); cx.notify_view(self.window_id, view_id);
} }
let mut dispatch_path = Vec::new();
for directive in dispatch_directives { for directive in dispatch_directives {
cx.dispatch_action_any(self.window_id, &directive.path, directive.action.as_ref()); dispatch_path.clear();
if let Some(view_id) = directive.dispatcher_view_id {
self.compute_dispatch_path_from(view_id, &mut dispatch_path);
}
cx.dispatch_action_any(self.window_id, &dispatch_path, directive.action.as_ref());
} }
} }
} }
@ -255,7 +448,7 @@ impl Presenter {
} }
pub struct DispatchDirective { pub struct DispatchDirective {
pub path: Vec<usize>, pub dispatcher_view_id: Option<usize>,
pub action: Box<dyn Action>, pub action: Box<dyn Action>,
} }
@ -264,16 +457,28 @@ pub struct LayoutContext<'a> {
rendered_views: &'a mut HashMap<usize, ElementBox>, rendered_views: &'a mut HashMap<usize, ElementBox>,
parents: &'a mut HashMap<usize, usize>, parents: &'a mut HashMap<usize, usize>,
view_stack: Vec<usize>, view_stack: Vec<usize>,
pub refreshing: bool,
pub window_size: Vector2F,
pub font_cache: &'a Arc<FontCache>, pub font_cache: &'a Arc<FontCache>,
pub font_system: Arc<dyn FontSystem>, pub font_system: Arc<dyn FontSystem>,
pub text_layout_cache: &'a TextLayoutCache, pub text_layout_cache: &'a TextLayoutCache,
pub asset_cache: &'a AssetCache, pub asset_cache: &'a AssetCache,
pub app: &'a mut MutableAppContext, pub app: &'a mut MutableAppContext,
pub refreshing: bool,
pub window_size: Vector2F,
titlebar_height: f32,
hovered_region_ids: HashSet<MouseRegionId>,
clicked_region_id: Option<MouseRegionId>,
right_clicked_region_id: Option<MouseRegionId>,
} }
impl<'a> LayoutContext<'a> { impl<'a> LayoutContext<'a> {
pub(crate) fn keystrokes_for_action(
&self,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.app
.keystrokes_for_action(self.window_id, &self.view_stack, action)
}
fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
if let Some(parent_id) = self.view_stack.last() { if let Some(parent_id) = self.view_stack.last() {
self.parents.insert(view_id, *parent_id); self.parents.insert(view_id, *parent_id);
@ -286,12 +491,25 @@ impl<'a> LayoutContext<'a> {
size size
} }
pub(crate) fn keystrokes_for_action( pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
&self, where
action: &dyn Action, F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
) -> Option<SmallVec<[Keystroke; 2]>> { V: View,
self.app {
.keystrokes_for_action(self.window_id, &self.view_stack, action) handle.update(self.app, |view, cx| {
let mut render_cx = RenderContext {
app: cx,
window_id: handle.window_id(),
view_id: handle.id(),
view_type: PhantomData,
titlebar_height: self.titlebar_height,
hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_id: self.clicked_region_id,
right_clicked_region_id: self.right_clicked_region_id,
refreshing: self.refreshing,
};
f(view, &mut render_cx)
})
} }
} }
@ -348,14 +566,9 @@ impl<'a> UpgradeViewHandle for LayoutContext<'a> {
} }
} }
impl<'a> ElementStateContext for LayoutContext<'a> {
fn current_view_id(&self) -> usize {
*self.view_stack.last().unwrap()
}
}
pub struct PaintContext<'a> { pub struct PaintContext<'a> {
rendered_views: &'a mut HashMap<usize, ElementBox>, rendered_views: &'a mut HashMap<usize, ElementBox>,
view_stack: Vec<usize>,
pub scene: &'a mut Scene, pub scene: &'a mut Scene,
pub font_cache: &'a FontCache, pub font_cache: &'a FontCache,
pub text_layout_cache: &'a TextLayoutCache, pub text_layout_cache: &'a TextLayoutCache,
@ -365,10 +578,16 @@ pub struct PaintContext<'a> {
impl<'a> PaintContext<'a> { impl<'a> PaintContext<'a> {
fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) { fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) {
if let Some(mut tree) = self.rendered_views.remove(&view_id) { if let Some(mut tree) = self.rendered_views.remove(&view_id) {
self.view_stack.push(view_id);
tree.paint(origin, visible_bounds, self); tree.paint(origin, visible_bounds, self);
self.rendered_views.insert(view_id, tree); self.rendered_views.insert(view_id, tree);
self.view_stack.pop();
} }
} }
pub fn current_view_id(&self) -> usize {
*self.view_stack.last().unwrap()
}
} }
impl<'a> Deref for PaintContext<'a> { impl<'a> Deref for PaintContext<'a> {
@ -393,9 +612,8 @@ pub struct EventContext<'a> {
impl<'a> EventContext<'a> { impl<'a> EventContext<'a> {
fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool { fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
if let Some(mut element) = self.rendered_views.remove(&view_id) { if let Some(mut element) = self.rendered_views.remove(&view_id) {
self.view_stack.push(view_id); let result =
let result = element.dispatch_event(event, self); self.with_current_view(view_id, |this| element.dispatch_event(event, this));
self.view_stack.pop();
self.rendered_views.insert(view_id, element); self.rendered_views.insert(view_id, element);
result result
} else { } else {
@ -403,9 +621,19 @@ impl<'a> EventContext<'a> {
} }
} }
fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
where
F: FnOnce(&mut Self) -> T,
{
self.view_stack.push(view_id);
let result = f(self);
self.view_stack.pop();
result
}
pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) { pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
self.dispatched_actions.push(DispatchDirective { self.dispatched_actions.push(DispatchDirective {
path: self.view_stack.clone(), dispatcher_view_id: self.view_stack.last().copied(),
action, action,
}); });
} }

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use std::{borrow::Cow, sync::Arc}; use std::{any::TypeId, borrow::Cow, rc::Rc, sync::Arc};
use crate::{ use crate::{
color::Color, color::Color,
@ -8,7 +8,7 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::ToJson, json::ToJson,
platform::CursorStyle, platform::CursorStyle,
ImageData, EventContext, ImageData,
}; };
pub struct Scene { pub struct Scene {
@ -33,7 +33,35 @@ pub struct Layer {
image_glyphs: Vec<ImageGlyph>, image_glyphs: Vec<ImageGlyph>,
icons: Vec<Icon>, icons: Vec<Icon>,
paths: Vec<Path>, paths: Vec<Path>,
cursor_styles: Vec<(RectF, CursorStyle)>, cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<MouseRegion>,
}
#[derive(Copy, Clone)]
pub struct CursorRegion {
pub bounds: RectF,
pub style: CursorStyle,
}
#[derive(Clone)]
pub struct MouseRegion {
pub view_id: usize,
pub tag: TypeId,
pub region_id: usize,
pub bounds: RectF,
pub hover: Option<Rc<dyn Fn(bool, &mut EventContext)>>,
pub mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
pub click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
pub right_mouse_down: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
pub right_click: Option<Rc<dyn Fn(Vector2F, usize, &mut EventContext)>>,
pub drag: Option<Rc<dyn Fn(Vector2F, &mut EventContext)>>,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct MouseRegionId {
pub view_id: usize,
pub tag: TypeId,
pub region_id: usize,
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -175,13 +203,20 @@ impl Scene {
self.stacking_contexts.iter().flat_map(|s| &s.layers) self.stacking_contexts.iter().flat_map(|s| &s.layers)
} }
pub fn cursor_styles(&self) -> Vec<(RectF, CursorStyle)> { pub fn cursor_regions(&self) -> Vec<CursorRegion> {
self.layers() self.layers()
.flat_map(|layer| &layer.cursor_styles) .flat_map(|layer| &layer.cursor_regions)
.copied() .copied()
.collect() .collect()
} }
pub fn mouse_regions(&self) -> Vec<MouseRegion> {
self.layers()
.flat_map(|layer| &layer.mouse_regions)
.cloned()
.collect()
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) { pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context_stack self.active_stacking_context_stack
.push(self.stacking_contexts.len()); .push(self.stacking_contexts.len());
@ -206,8 +241,12 @@ impl Scene {
self.active_layer().push_quad(quad) self.active_layer().push_quad(quad)
} }
pub fn push_cursor_style(&mut self, bounds: RectF, style: CursorStyle) { pub fn push_cursor_region(&mut self, region: CursorRegion) {
self.active_layer().push_cursor_style(bounds, style); self.active_layer().push_cursor_region(region);
}
pub fn push_mouse_region(&mut self, region: MouseRegion) {
self.active_layer().push_mouse_region(region);
} }
pub fn push_image(&mut self, image: Image) { pub fn push_image(&mut self, image: Image) {
@ -298,7 +337,8 @@ impl Layer {
glyphs: Default::default(), glyphs: Default::default(),
icons: Default::default(), icons: Default::default(),
paths: Default::default(), paths: Default::default(),
cursor_styles: Default::default(), cursor_regions: Default::default(),
mouse_regions: Default::default(),
} }
} }
@ -316,10 +356,24 @@ impl Layer {
self.quads.as_slice() self.quads.as_slice()
} }
fn push_cursor_style(&mut self, bounds: RectF, style: CursorStyle) { fn push_cursor_region(&mut self, region: CursorRegion) {
if let Some(bounds) = bounds.intersection(self.clip_bounds.unwrap_or(bounds)) { if let Some(bounds) = region
.bounds
.intersection(self.clip_bounds.unwrap_or(region.bounds))
{
if can_draw(bounds) { if can_draw(bounds) {
self.cursor_styles.push((bounds, style)); self.cursor_regions.push(region);
}
}
}
fn push_mouse_region(&mut self, region: MouseRegion) {
if let Some(bounds) = region
.bounds
.intersection(self.clip_bounds.unwrap_or(region.bounds))
{
if can_draw(bounds) {
self.mouse_regions.push(region);
} }
} }
} }
@ -484,6 +538,16 @@ impl ToJson for Border {
} }
} }
impl MouseRegion {
pub fn id(&self) -> MouseRegionId {
MouseRegionId {
view_id: self.view_id,
tag: self.tag,
region_id: self.region_id,
}
}
}
fn can_draw(bounds: RectF) -> bool { fn can_draw(bounds: RectF) -> bool {
let size = bounds.size(); let size = bounds.size();
size.x() > 0. && size.y() > 0. size.x() > 0. && size.y() > 0.

View file

@ -123,7 +123,6 @@ impl View for Select {
.boxed(), .boxed(),
); );
if self.is_open { if self.is_open {
let handle = self.handle.clone();
result.add_child( result.add_child(
Overlay::new( Overlay::new(
Container::new( Container::new(
@ -131,9 +130,8 @@ impl View for Select {
UniformList::new( UniformList::new(
self.list_state.clone(), self.list_state.clone(),
self.item_count, self.item_count,
move |mut range, items, cx| { cx,
let handle = handle.upgrade(cx).unwrap(); move |this, mut range, items, cx| {
let this = handle.read(cx);
let selected_item_ix = this.selected_item_ix; let selected_item_ix = this.selected_item_ix;
range.end = range.end.min(this.item_count); range.end = range.end.min(this.item_count);
items.extend(range.map(|ix| { items.extend(range.map(|ix| {
@ -141,7 +139,7 @@ impl View for Select {
ix, ix,
cx, cx,
|mouse_state, cx| { |mouse_state, cx| {
(handle.read(cx).render_item)( (this.render_item)(
ix, ix,
if ix == selected_item_ix { if ix == selected_item_ix {
ItemType::Selected ItemType::Selected

View file

@ -4,8 +4,8 @@ use editor::{
}; };
use fuzzy::StringMatch; use fuzzy::StringMatch;
use gpui::{ use gpui::{
actions, elements::*, geometry::vector::Vector2F, AppContext, Entity, MutableAppContext, actions, elements::*, geometry::vector::Vector2F, AppContext, Entity, MouseState,
RenderContext, Task, View, ViewContext, ViewHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
}; };
use language::Outline; use language::Outline;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
@ -231,7 +231,7 @@ impl PickerDelegate for OutlineView {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &MouseState, mouse_state: MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> ElementBox {

View file

@ -1,14 +1,14 @@
use editor::Editor; use editor::Editor;
use gpui::{ use gpui::{
elements::{ elements::{
ChildView, Flex, Label, MouseEventHandler, MouseState, ParentElement, ScrollTarget, ChildView, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, UniformList,
UniformList, UniformListState, UniformListState,
}, },
geometry::vector::{vec2f, Vector2F}, geometry::vector::{vec2f, Vector2F},
keymap, keymap,
platform::CursorStyle, platform::CursorStyle,
AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task, View, AppContext, Axis, Element, ElementBox, Entity, MouseState, MutableAppContext, RenderContext,
ViewContext, ViewHandle, WeakViewHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev}; use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
use settings::Settings; use settings::Settings;
@ -32,7 +32,7 @@ pub trait PickerDelegate: View {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
state: &MouseState, state: MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox; ) -> ElementBox;
@ -52,6 +52,7 @@ impl<D: PickerDelegate> View for Picker<D> {
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
let settings = cx.global::<Settings>(); let settings = cx.global::<Settings>();
let container_style = settings.theme.picker.container;
let delegate = self.delegate.clone(); let delegate = self.delegate.clone();
let match_count = if let Some(delegate) = delegate.upgrade(cx.app) { let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
delegate.read(cx).match_count() delegate.read(cx).match_count()
@ -78,8 +79,9 @@ impl<D: PickerDelegate> View for Picker<D> {
UniformList::new( UniformList::new(
self.list_state.clone(), self.list_state.clone(),
match_count, match_count,
move |mut range, items, cx| { cx,
let delegate = delegate.upgrade(cx).unwrap(); move |this, mut range, items, cx| {
let delegate = this.delegate.upgrade(cx).unwrap();
let selected_ix = delegate.read(cx).selected_index(); let selected_ix = delegate.read(cx).selected_index();
range.end = cmp::min(range.end, delegate.read(cx).match_count()); range.end = cmp::min(range.end, delegate.read(cx).match_count());
items.extend(range.map(move |ix| { items.extend(range.map(move |ix| {
@ -101,7 +103,7 @@ impl<D: PickerDelegate> View for Picker<D> {
.boxed(), .boxed(),
) )
.contained() .contained()
.with_style(settings.theme.picker.container) .with_style(container_style)
.constrained() .constrained()
.with_max_width(self.max_size.x()) .with_max_width(self.max_size.x())
.with_max_height(self.max_size.y()) .with_max_height(self.max_size.y())

View file

@ -12,7 +12,7 @@ use gpui::{
impl_internal_actions, keymap, impl_internal_actions, keymap,
platform::CursorStyle, platform::CursorStyle,
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MutableAppContext,
PromptLevel, Task, View, ViewContext, ViewHandle, WeakViewHandle, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@ -795,8 +795,8 @@ impl ProjectPanel {
fn for_each_visible_entry( fn for_each_visible_entry(
&self, &self,
range: Range<usize>, range: Range<usize>,
cx: &mut ViewContext<ProjectPanel>, cx: &mut RenderContext<ProjectPanel>,
mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<ProjectPanel>), mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut RenderContext<ProjectPanel>),
) { ) {
let mut ix = 0; let mut ix = 0;
for (worktree_id, visible_worktree_entries) in &self.visible_entries { for (worktree_id, visible_worktree_entries) in &self.visible_entries {
@ -869,7 +869,7 @@ impl ProjectPanel {
details: EntryDetails, details: EntryDetails,
editor: &ViewHandle<Editor>, editor: &ViewHandle<Editor>,
theme: &theme::ProjectPanel, theme: &theme::ProjectPanel,
cx: &mut ViewContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> ElementBox {
let kind = details.kind; let kind = details.kind;
let show_editor = details.is_editing && !details.is_processing; let show_editor = details.is_editing && !details.is_processing;
@ -961,24 +961,19 @@ impl View for ProjectPanel {
let theme = &cx.global::<Settings>().theme.project_panel; let theme = &cx.global::<Settings>().theme.project_panel;
let mut container_style = theme.container; let mut container_style = theme.container;
let padding = std::mem::take(&mut container_style.padding); let padding = std::mem::take(&mut container_style.padding);
let handle = self.handle.clone();
Stack::new() Stack::new()
.with_child( .with_child(
MouseEventHandler::new::<Tag, _, _>(0, cx, |_, _| { MouseEventHandler::new::<Tag, _, _>(0, cx, |_, cx| {
UniformList::new( UniformList::new(
self.list.clone(), self.list.clone(),
self.visible_entries self.visible_entries
.iter() .iter()
.map(|(_, worktree_entries)| worktree_entries.len()) .map(|(_, worktree_entries)| worktree_entries.len())
.sum(), .sum(),
move |range, items, cx| {
let theme = cx.global::<Settings>().theme.clone();
let this = handle.upgrade(cx).unwrap();
this.update(cx.app, |this, cx| {
this.for_each_visible_entry(
range.clone(),
cx, cx,
|id, details, cx| { move |this, range, items, cx| {
let theme = cx.global::<Settings>().theme.clone();
this.for_each_visible_entry(range.clone(), cx, |id, details, cx| {
items.push(Self::render_entry( items.push(Self::render_entry(
id, id,
details, details,
@ -986,9 +981,7 @@ impl View for ProjectPanel {
&theme.project_panel, &theme.project_panel,
cx, cx,
)); ));
}, });
);
})
}, },
) )
.with_padding_top(padding.top) .with_padding_top(padding.top)
@ -1458,7 +1451,7 @@ mod tests {
let mut result = Vec::new(); let mut result = Vec::new();
let mut project_entries = HashSet::new(); let mut project_entries = HashSet::new();
let mut has_editor = false; let mut has_editor = false;
panel.update(cx, |panel, cx| { cx.render(panel, |panel, cx| {
panel.for_each_visible_entry(range, cx, |project_entry, details, _| { panel.for_each_visible_entry(range, cx, |project_entry, details, _| {
if details.is_editing { if details.is_editing {
assert!(!has_editor, "duplicate editor entry"); assert!(!has_editor, "duplicate editor entry");

View file

@ -3,8 +3,8 @@ use editor::{
}; };
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, elements::*, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Task, actions, elements::*, AppContext, Entity, ModelHandle, MouseState, MutableAppContext,
View, ViewContext, ViewHandle, RenderContext, Task, View, ViewContext, ViewHandle,
}; };
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
@ -221,7 +221,7 @@ impl PickerDelegate for ProjectSymbolsView {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &MouseState, mouse_state: MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> ElementBox {

View file

@ -2,9 +2,9 @@ mod theme_registry;
use gpui::{ use gpui::{
color::Color, color::Color,
elements::{ContainerStyle, ImageStyle, LabelStyle, MouseState}, elements::{ContainerStyle, ImageStyle, LabelStyle},
fonts::{HighlightStyle, TextStyle}, fonts::{HighlightStyle, TextStyle},
Border, Border, MouseState,
}; };
use serde::{de::DeserializeOwned, Deserialize}; use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value; use serde_json::Value;
@ -505,7 +505,7 @@ pub struct Interactive<T> {
} }
impl<T> Interactive<T> { impl<T> Interactive<T> {
pub fn style_for(&self, state: &MouseState, active: bool) -> &T { pub fn style_for(&self, state: MouseState, active: bool) -> &T {
if active { if active {
if state.hovered { if state.hovered {
self.active_hover self.active_hover

View file

@ -1,6 +1,6 @@
use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, elements::*, AppContext, Element, ElementBox, Entity, MutableAppContext, actions, elements::*, AppContext, Element, ElementBox, Entity, MouseState, MutableAppContext,
RenderContext, View, ViewContext, ViewHandle, RenderContext, View, ViewContext, ViewHandle,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
@ -213,7 +213,7 @@ impl PickerDelegate for ThemeSelector {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
mouse_state: &MouseState, mouse_state: MouseState,
selected: bool, selected: bool,
cx: &AppContext, cx: &AppContext,
) -> ElementBox { ) -> ElementBox {

View file

@ -689,6 +689,7 @@ impl Pane {
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
enum Tabs {} enum Tabs {}
enum Tab {}
let pane = cx.handle(); let pane = cx.handle();
let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| { let tabs = MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| {
let autoscroll = if mem::take(&mut self.autoscroll) { let autoscroll = if mem::take(&mut self.autoscroll) {
@ -717,7 +718,7 @@ impl Pane {
style.container.border.left = false; style.container.border.left = false;
} }
EventHandler::new( MouseEventHandler::new::<Tab, _, _>(ix, cx, |_, cx| {
Container::new( Container::new(
Flex::row() Flex::row()
.with_child( .with_child(
@ -807,11 +808,10 @@ impl Pane {
.boxed(), .boxed(),
) )
.with_style(style.container) .with_style(style.container)
.boxed(), .boxed()
) })
.on_mouse_down(move |cx| { .on_mouse_down(move |_, cx| {
cx.dispatch_action(ActivateItem(ix)); cx.dispatch_action(ActivateItem(ix));
true
}) })
.boxed() .boxed()
}) })

View file

@ -165,6 +165,7 @@ impl Sidebar {
..Default::default() ..Default::default()
}) })
.with_cursor_style(CursorStyle::ResizeLeftRight) .with_cursor_style(CursorStyle::ResizeLeftRight)
.on_mouse_down(|_, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(move |delta, cx| { .on_drag(move |delta, cx| {
let prev_width = *actual_width.borrow(); let prev_width = *actual_width.borrow();
*custom_width.borrow_mut() = 0f32 *custom_width.borrow_mut() = 0f32