Merge branch 'main' into go-to-line2

This commit is contained in:
Mikayla 2023-11-08 11:32:36 -08:00
commit 409e17ad30
No known key found for this signature in database
71 changed files with 7160 additions and 5833 deletions

View file

@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
clean: false clean: false
submodules: 'recursive' submodules: "recursive"
- name: cargo fmt - name: cargo fmt
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
@ -56,13 +56,13 @@ jobs:
- name: Install Node - name: Install Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: '18' node-version: "18"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
clean: false clean: false
submodules: 'recursive' submodules: "recursive"
- name: Limit target directory size - name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70 run: script/clear-target-dir-if-larger-than 70
@ -79,9 +79,6 @@ jobs:
- name: Build other binaries - name: Build other binaries
run: cargo build --workspace --bins --all-features run: cargo build --workspace --bins --all-features
- name: Generate license file
run: script/generate-licenses
bundle: bundle:
name: Bundle app name: Bundle app
runs-on: runs-on:
@ -106,13 +103,13 @@ jobs:
- name: Install Node - name: Install Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: '18' node-version: "18"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
clean: false clean: false
submodules: 'recursive' submodules: "recursive"
- name: Limit target directory size - name: Limit target directory size
run: script/clear-target-dir-if-larger-than 70 run: script/clear-target-dir-if-larger-than 70

26
Cargo.lock generated
View file

@ -4988,6 +4988,7 @@ name = "menu2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"gpui2", "gpui2",
"serde",
] ]
[[package]] [[package]]
@ -6023,6 +6024,23 @@ dependencies = [
"workspace", "workspace",
] ]
[[package]]
name = "picker2"
version = "0.1.0"
dependencies = [
"ctor",
"editor2",
"env_logger 0.9.3",
"gpui2",
"menu2",
"parking_lot 0.11.2",
"serde_json",
"settings2",
"theme2",
"util",
"workspace2",
]
[[package]] [[package]]
name = "pico-args" name = "pico-args"
version = "0.4.2" version = "0.4.2"
@ -8539,9 +8557,14 @@ dependencies = [
"backtrace-on-stack-overflow", "backtrace-on-stack-overflow",
"chrono", "chrono",
"clap 4.4.4", "clap 4.4.4",
"editor2",
"fuzzy2",
"gpui2", "gpui2",
"itertools 0.11.0", "itertools 0.11.0",
"language2",
"log", "log",
"menu2",
"picker2",
"rust-embed", "rust-embed",
"serde", "serde",
"settings2", "settings2",
@ -9005,6 +9028,7 @@ dependencies = [
"fs2", "fs2",
"gpui2", "gpui2",
"indexmap 1.9.3", "indexmap 1.9.3",
"itertools 0.11.0",
"parking_lot 0.11.2", "parking_lot 0.11.2",
"refineable", "refineable",
"schemars", "schemars",
@ -11077,7 +11101,7 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.112.0" version = "0.113.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"ai", "ai",

View file

@ -68,6 +68,7 @@ members = [
"crates/notifications", "crates/notifications",
"crates/outline", "crates/outline",
"crates/picker", "crates/picker",
"crates/picker2",
"crates/plugin", "crates/plugin",
"crates/plugin_macros", "crates/plugin_macros",
"crates/plugin_runtime", "crates/plugin_runtime",

View file

@ -80,7 +80,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
init_settings(cx); init_settings(cx);
let client = Arc::downgrade(client); let client = Arc::downgrade(client);
cx.register_action_type::<SignIn>();
cx.on_action({ cx.on_action({
let client = client.clone(); let client = client.clone();
move |_: &SignIn, cx| { move |_: &SignIn, cx| {
@ -93,7 +92,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
} }
}); });
cx.register_action_type::<SignOut>();
cx.on_action({ cx.on_action({
let client = client.clone(); let client = client.clone();
move |_: &SignOut, cx| { move |_: &SignOut, cx| {
@ -106,7 +104,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
} }
}); });
cx.register_action_type::<Reconnect>();
cx.on_action({ cx.on_action({
let client = client.clone(); let client = client.clone();
move |_: &Reconnect, cx| { move |_: &Reconnect, cx| {

View file

@ -7,8 +7,8 @@ use async_tar::Archive;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model, ModelContext, actions, AppContext, AsyncAppContext, Context, Entity, EntityId, EventEmitter, Model,
Task, WeakModel, ModelContext, Task, WeakModel,
}; };
use language::{ use language::{
language_settings::{all_language_settings, language_settings}, language_settings::{all_language_settings, language_settings},
@ -34,19 +34,11 @@ use util::{
// todo!() // todo!()
// const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth"; // const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
// actions!(copilot_auth, [SignIn, SignOut]); actions!(SignIn, SignOut);
// todo!() // todo!()
// const COPILOT_NAMESPACE: &'static str = "copilot"; // const COPILOT_NAMESPACE: &'static str = "copilot";
// actions!( actions!(Suggest, NextSuggestion, PreviousSuggestion, Reinstall);
// copilot,
// [Suggest, NextSuggestion, PreviousSuggestion, Reinstall]
// );
//
pub struct Suggest;
pub struct NextSuggestion;
pub struct PreviousSuggestion;
pub struct Reinstall;
pub fn init( pub fn init(
new_server_id: LanguageServerId, new_server_id: LanguageServerId,

File diff suppressed because it is too large Load diff

View file

@ -2,17 +2,25 @@ use crate::{
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint}, display_map::{BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint},
editor_settings::ShowScrollbar, editor_settings::ShowScrollbar,
git::{diff_hunk_to_display, DisplayDiffHunk}, git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::hover_at,
link_go_to_definition::{
go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
update_inlay_link_and_hover_points, GoToDefinitionTrigger,
},
scroll::scroll_amount::ScrollAmount,
CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
MoveDown, Point, Selection, SoftWrap, ToPoint, MAX_LINE_LEN, HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, PageDown, PageUp, Point, SelectPhase,
Selection, SoftWrap, ToPoint, MAX_LINE_LEN,
}; };
use anyhow::Result; use anyhow::Result;
use collections::{BTreeMap, HashMap}; use collections::{BTreeMap, HashMap};
use gpui::{ use gpui::{
black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, black, hsla, point, px, relative, size, transparent_black, Action, AnyElement,
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
Edges, Element, ElementId, Entity, Hsla, KeyDownEvent, KeyListener, KeyMatch, Line, Pixels, Edges, Element, ElementId, Entity, GlobalElementId, Hsla, KeyDownEvent, KeyListener, KeyMatch,
ScrollWheelEvent, ShapedGlyph, Size, StatefulInteraction, Style, TextRun, TextStyle, Line, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
TextSystem, ViewContext, WindowContext, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext,
WindowContext,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting; use language::language_settings::ShowWhitespaceSetting;
@ -31,6 +39,7 @@ use std::{
}; };
use sum_tree::Bias; use sum_tree::Bias;
use theme::{ActiveTheme, PlayerColor}; use theme::{ActiveTheme, PlayerColor};
use util::ResultExt;
use workspace::item::Item; use workspace::item::Item;
enum FoldMarkers {} enum FoldMarkers {}
@ -104,194 +113,54 @@ impl EditorElement {
Self { style } Self { style }
} }
// fn attach_mouse_handlers( fn mouse_down(
// position_map: &Arc<PositionMap>, editor: &mut Editor,
// has_popovers: bool, event: &MouseDownEvent,
// visible_bounds: Bounds<Pixels>, position_map: &PositionMap,
// text_bounds: Bounds<Pixels>, text_bounds: Bounds<Pixels>,
// gutter_bounds: Bounds<Pixels>, gutter_bounds: Bounds<Pixels>,
// bounds: Bounds<Pixels>, cx: &mut ViewContext<Editor>,
// cx: &mut ViewContext<Editor>, ) -> bool {
// ) { let mut click_count = event.click_count;
// enum EditorElementMouseHandlers {} let modifiers = event.modifiers;
// let view_id = cx.view_id();
// cx.scene().push_mouse_region(
// MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
// .on_down(MouseButton::Left, {
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if !Self::mouse_down(
// editor,
// event.platform_event,
// position_map.as_ref(),
// text_bounds,
// gutter_bounds,
// cx,
// ) {
// cx.propagate_event();
// }
// }
// })
// .on_down(MouseButton::Right, {
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if !Self::mouse_right_down(
// editor,
// event.position,
// position_map.as_ref(),
// text_bounds,
// cx,
// ) {
// cx.propagate_event();
// }
// }
// })
// .on_up(MouseButton::Left, {
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if !Self::mouse_up(
// editor,
// event.position,
// event.cmd,
// event.shift,
// event.alt,
// position_map.as_ref(),
// text_bounds,
// cx,
// ) {
// cx.propagate_event()
// }
// }
// })
// .on_drag(MouseButton::Left, {
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if event.end {
// return;
// }
// if !Self::mouse_dragged( if gutter_bounds.contains_point(&event.position) {
// editor, click_count = 3; // Simulate triple-click when clicking the gutter to select lines
// event.platform_event, } else if !text_bounds.contains_point(&event.position) {
// position_map.as_ref(), return false;
// text_bounds, }
// cx,
// ) {
// cx.propagate_event()
// }
// }
// })
// .on_move({
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if !Self::mouse_moved(
// editor,
// event.platform_event,
// &position_map,
// text_bounds,
// cx,
// ) {
// cx.propagate_event()
// }
// }
// })
// .on_move_out(move |_, editor: &mut Editor, cx| {
// if has_popovers {
// hide_hover(editor, cx);
// }
// })
// .on_scroll({
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if !Self::scroll(
// editor,
// event.position,
// *event.delta.raw(),
// event.delta.precise(),
// &position_map,
// bounds,
// cx,
// ) {
// cx.propagate_event()
// }
// }
// }),
// );
// enum GutterHandlers {} let point_for_position = position_map.point_for_position(text_bounds, event.position);
// let view_id = cx.view_id(); let position = point_for_position.previous_valid;
// let region_id = cx.view_id() + 1; if modifiers.shift && modifiers.alt {
// cx.scene().push_mouse_region( editor.select(
// MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover( SelectPhase::BeginColumnar {
// |hover, editor: &mut Editor, cx| { position,
// editor.gutter_hover( goal_column: point_for_position.exact_unclipped.column(),
// &GutterHover { },
// hovered: hover.started, cx,
// }, );
// cx, } else if modifiers.shift && !modifiers.control && !modifiers.alt && !modifiers.command {
// ); editor.select(
// }, SelectPhase::Extend {
// ), position,
// ) click_count,
// } },
cx,
);
} else {
editor.select(
SelectPhase::Begin {
position,
add: modifiers.alt,
click_count,
},
cx,
);
}
// fn mouse_down( true
// editor: &mut Editor, }
// MouseButtonEvent {
// position,
// modifiers:
// Modifiers {
// shift,
// ctrl,
// alt,
// cmd,
// ..
// },
// mut click_count,
// ..
// }: MouseButtonEvent,
// position_map: &PositionMap,
// text_bounds: Bounds<Pixels>,
// gutter_bounds: Bounds<Pixels>,
// cx: &mut EventContext<Editor>,
// ) -> bool {
// if gutter_bounds.contains_point(position) {
// click_count = 3; // Simulate triple-click when clicking the gutter to select lines
// } else if !text_bounds.contains_point(position) {
// return false;
// }
// let point_for_position = position_map.point_for_position(text_bounds, position);
// let position = point_for_position.previous_valid;
// if shift && alt {
// editor.select(
// SelectPhase::BeginColumnar {
// position,
// goal_column: point_for_position.exact_unclipped.column(),
// },
// cx,
// );
// } else if shift && !ctrl && !alt && !cmd {
// editor.select(
// SelectPhase::Extend {
// position,
// click_count,
// },
// cx,
// );
// } else {
// editor.select(
// SelectPhase::Begin {
// position,
// add: alt,
// click_count,
// },
// cx,
// );
// }
// true
// }
// fn mouse_right_down( // fn mouse_right_down(
// editor: &mut Editor, // editor: &mut Editor,
@ -313,157 +182,120 @@ impl EditorElement {
// true // true
// } // }
// fn mouse_up( fn mouse_up(
// editor: &mut Editor, editor: &mut Editor,
// position: gpui::Point<Pixels>, event: &MouseUpEvent,
// cmd: bool, position_map: &PositionMap,
// shift: bool, text_bounds: Bounds<Pixels>,
// alt: bool, cx: &mut ViewContext<Editor>,
// position_map: &PositionMap, ) -> bool {
// text_bounds: Bounds<Pixels>, let end_selection = editor.has_pending_selection();
// cx: &mut EventContext<Editor>, let pending_nonempty_selections = editor.has_pending_nonempty_selection();
// ) -> bool {
// let end_selection = editor.has_pending_selection();
// let pending_nonempty_selections = editor.has_pending_nonempty_selection();
// if end_selection { if end_selection {
// editor.select(SelectPhase::End, cx); editor.select(SelectPhase::End, cx);
// } }
// if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) { if !pending_nonempty_selections
// let point = position_map.point_for_position(text_bounds, position); && event.modifiers.command
// let could_be_inlay = point.as_valid().is_none(); && text_bounds.contains_point(&event.position)
// if shift || could_be_inlay { {
// go_to_fetched_type_definition(editor, point, alt, cx); let point = position_map.point_for_position(text_bounds, event.position);
// } else { let could_be_inlay = point.as_valid().is_none();
// go_to_fetched_definition(editor, point, alt, cx); let split = event.modifiers.alt;
// } if event.modifiers.shift || could_be_inlay {
go_to_fetched_type_definition(editor, point, split, cx);
} else {
go_to_fetched_definition(editor, point, split, cx);
}
// return true; return true;
// } }
// end_selection end_selection
// } }
// fn mouse_dragged( fn mouse_moved(
// editor: &mut Editor, editor: &mut Editor,
// MouseMovedEvent { event: &MouseMoveEvent,
// modifiers: Modifiers { cmd, shift, .. }, position_map: &PositionMap,
// position, text_bounds: Bounds<Pixels>,
// .. gutter_bounds: Bounds<Pixels>,
// }: MouseMovedEvent, cx: &mut ViewContext<Editor>,
// position_map: &PositionMap, ) -> bool {
// text_bounds: Bounds<Pixels>, let modifiers = event.modifiers;
// cx: &mut EventContext<Editor>, if editor.has_pending_selection() && event.pressed_button == Some(MouseButton::Left) {
// ) -> bool { let point_for_position = position_map.point_for_position(text_bounds, event.position);
// // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed let mut scroll_delta = gpui::Point::<f32>::zero();
// // Don't trigger hover popover if mouse is hovering over context menu let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0);
// let point = if text_bounds.contains_point(position) { let top = text_bounds.origin.y + vertical_margin;
// position_map let bottom = text_bounds.lower_left().y - vertical_margin;
// .point_for_position(text_bounds, position) if event.position.y < top {
// .as_valid() scroll_delta.y = -scale_vertical_mouse_autoscroll_delta(top - event.position.y);
// } else { }
// None if event.position.y > bottom {
// }; scroll_delta.y = scale_vertical_mouse_autoscroll_delta(event.position.y - bottom);
}
// update_go_to_definition_link( let horizontal_margin = position_map.line_height.min(text_bounds.size.width / 3.0);
// editor, let left = text_bounds.origin.x + horizontal_margin;
// point.map(GoToDefinitionTrigger::Text), let right = text_bounds.upper_right().x - horizontal_margin;
// cmd, if event.position.x < left {
// shift, scroll_delta.x = -scale_horizontal_mouse_autoscroll_delta(left - event.position.x);
// cx, }
// ); if event.position.x > right {
scroll_delta.x = scale_horizontal_mouse_autoscroll_delta(event.position.x - right);
}
// if editor.has_pending_selection() { editor.select(
// let mut scroll_delta = gpui::Point::<Pixels>::zero(); SelectPhase::Update {
position: point_for_position.previous_valid,
goal_column: point_for_position.exact_unclipped.column(),
scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
.clamp(&gpui::Point::zero(), &position_map.scroll_max),
},
cx,
);
}
// let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0); let text_hovered = text_bounds.contains_point(&event.position);
// let top = text_bounds.origin.y + vertical_margin; let gutter_hovered = gutter_bounds.contains_point(&event.position);
// let bottom = text_bounds.lower_left().y - vertical_margin; editor.set_gutter_hovered(gutter_hovered, cx);
// if position.y < top {
// scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y))
// }
// if position.y > bottom {
// scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y - bottom))
// }
// let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0); // Don't trigger hover popover if mouse is hovering over context menu
// let left = text_bounds.origin.x + horizontal_margin; if text_hovered {
// let right = text_bounds.upper_right().x - horizontal_margin; let point_for_position = position_map.point_for_position(text_bounds, event.position);
// if position.x < left {
// scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
// left - position.x,
// ))
// }
// if position.x > right {
// scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
// position.x - right,
// ))
// }
// let point_for_position = position_map.point_for_position(text_bounds, position); match point_for_position.as_valid() {
Some(point) => {
update_go_to_definition_link(
editor,
Some(GoToDefinitionTrigger::Text(point)),
modifiers.command,
modifiers.shift,
cx,
);
hover_at(editor, Some(point), cx);
}
None => {
update_inlay_link_and_hover_points(
&position_map.snapshot,
point_for_position,
editor,
modifiers.command,
modifiers.shift,
cx,
);
}
}
// editor.select( true
// SelectPhase::Update { } else {
// position: point_for_position.previous_valid, update_go_to_definition_link(editor, None, modifiers.command, modifiers.shift, cx);
// goal_column: point_for_position.exact_unclipped.column(), hover_at(editor, None, cx);
// scroll_position: (position_map.snapshot.scroll_position() + scroll_delta) gutter_hovered
// .clamp(gpui::Point::<Pixels>::zero(), position_map.scroll_max), }
// }, }
// cx,
// );
// hover_at(editor, point, cx);
// true
// } else {
// hover_at(editor, point, cx);
// false
// }
// }
// fn mouse_moved(
// editor: &mut Editor,
// MouseMovedEvent {
// modifiers: Modifiers { shift, cmd, .. },
// position,
// ..
// }: MouseMovedEvent,
// position_map: &PositionMap,
// text_bounds: Bounds<Pixels>,
// cx: &mut ViewContext<Editor>,
// ) -> bool {
// // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
// // Don't trigger hover popover if mouse is hovering over context menu
// if text_bounds.contains_point(position) {
// let point_for_position = position_map.point_for_position(text_bounds, position);
// match point_for_position.as_valid() {
// Some(point) => {
// update_go_to_definition_link(
// editor,
// Some(GoToDefinitionTrigger::Text(point)),
// cmd,
// shift,
// cx,
// );
// hover_at(editor, Some(point), cx);
// }
// None => {
// update_inlay_link_and_hover_points(
// &position_map.snapshot,
// point_for_position,
// editor,
// cmd,
// shift,
// cx,
// );
// }
// }
// } else {
// update_go_to_definition_link(editor, None, cmd, shift, cx);
// hover_at(editor, None, cx);
// }
// true
// }
fn scroll( fn scroll(
editor: &mut Editor, editor: &mut Editor,
@ -2337,6 +2169,79 @@ impl EditorElement {
// blocks, // blocks,
// ) // )
// } // }
fn paint_mouse_listeners(
&mut self,
bounds: Bounds<Pixels>,
gutter_bounds: Bounds<Pixels>,
text_bounds: Bounds<Pixels>,
position_map: &Arc<PositionMap>,
cx: &mut ViewContext<Editor>,
) {
cx.on_mouse_event({
let position_map = position_map.clone();
move |editor, event: &ScrollWheelEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if Self::scroll(editor, event, &position_map, bounds, cx) {
cx.stop_propagation();
}
}
});
cx.on_mouse_event({
let position_map = position_map.clone();
move |editor, event: &MouseDownEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if Self::mouse_down(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
cx.stop_propagation()
}
}
});
cx.on_mouse_event({
let position_map = position_map.clone();
move |editor, event: &MouseUpEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if Self::mouse_up(editor, event, &position_map, text_bounds, cx) {
cx.stop_propagation()
}
}
});
// todo!()
// on_down(MouseButton::Right, {
// let position_map = position_map.clone();
// move |event, editor, cx| {
// if !Self::mouse_right_down(
// editor,
// event.position,
// position_map.as_ref(),
// text_bounds,
// cx,
// ) {
// cx.propagate_event();
// }
// }
// });
cx.on_mouse_event({
let position_map = position_map.clone();
move |editor, event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if Self::mouse_moved(editor, event, &position_map, text_bounds, gutter_bounds, cx) {
cx.stop_propagation()
}
}
});
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -2556,30 +2461,9 @@ impl Element<Editor> for EditorElement {
let dispatch_context = editor.dispatch_context(cx); let dispatch_context = editor.dispatch_context(cx);
cx.with_element_id(cx.view().entity_id(), |global_id, cx| { cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
cx.with_key_dispatch_context(dispatch_context, |cx| { cx.with_key_dispatch_context(dispatch_context, |cx| {
cx.with_key_listeners( cx.with_key_listeners(build_key_listeners(global_id), |cx| {
[ cx.with_focus(editor.focus_handle.clone(), |_| {})
build_action_listener(Editor::move_left), });
build_action_listener(Editor::move_right),
build_action_listener(Editor::move_down),
build_action_listener(Editor::move_up),
build_key_listener(
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
if phase == DispatchPhase::Bubble {
if let KeyMatch::Some(action) = cx.match_keystroke(
&global_id,
&key_down.keystroke,
dispatch_context,
) {
return Some(action);
}
}
None
},
),
],
|cx| cx.with_focus(editor.focus_handle.clone(), |_| {}),
);
}) })
}); });
} }
@ -2609,21 +2493,6 @@ impl Element<Editor> for EditorElement {
cx: &mut gpui::ViewContext<Editor>, cx: &mut gpui::ViewContext<Editor>,
) { ) {
let layout = self.compute_layout(editor, cx, bounds); let layout = self.compute_layout(editor, cx, bounds);
cx.on_mouse_event({
let position_map = layout.position_map.clone();
move |editor, event: &ScrollWheelEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
if Self::scroll(editor, event, &position_map, bounds, cx) {
cx.stop_propagation();
}
}
});
cx.with_content_mask(ContentMask { bounds }, |cx| {
let gutter_bounds = Bounds { let gutter_bounds = Bounds {
origin: bounds.origin, origin: bounds.origin,
size: layout.gutter_size, size: layout.gutter_size,
@ -2633,6 +2502,18 @@ impl Element<Editor> for EditorElement {
size: layout.text_size, size: layout.text_size,
}; };
if editor.focus_handle.is_focused(cx) {
cx.handle_text_input();
}
cx.with_content_mask(ContentMask { bounds }, |cx| {
self.paint_mouse_listeners(
bounds,
gutter_bounds,
text_bounds,
&layout.position_map,
cx,
);
self.paint_background(gutter_bounds, text_bounds, &layout, cx); self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO { if layout.gutter_size.width > Pixels::ZERO {
self.paint_gutter(gutter_bounds, &layout, editor, cx); self.paint_gutter(gutter_bounds, &layout, editor, cx);
@ -3339,7 +3220,7 @@ impl PositionMap {
let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left); let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right); let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into(); let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
*exact_unclipped.column_mut() += column_overshoot_after_line_end; *exact_unclipped.column_mut() += column_overshoot_after_line_end;
PointForPosition { PointForPosition {
previous_valid, previous_valid,
@ -3661,12 +3542,12 @@ impl HighlightedRange {
// bounds.into_iter() // bounds.into_iter()
// } // }
pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { pub fn scale_vertical_mouse_autoscroll_delta(delta: Pixels) -> f32 {
delta.powf(1.5) / 100.0 (delta.pow(1.5) / 100.0).into()
} }
fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
delta.powf(1.2) / 300.0 (delta.pow(1.2) / 300.0).into()
} }
// #[cfg(test)] // #[cfg(test)]
@ -4112,6 +3993,178 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
// } // }
// } // }
fn build_key_listeners(
global_element_id: GlobalElementId,
) -> impl IntoIterator<Item = (TypeId, KeyListener<Editor>)> {
[
build_action_listener(Editor::move_left),
build_action_listener(Editor::move_right),
build_action_listener(Editor::move_down),
build_action_listener(Editor::move_up),
// build_action_listener(Editor::new_file), todo!()
// build_action_listener(Editor::new_file_in_direction), todo!()
build_action_listener(Editor::cancel),
build_action_listener(Editor::newline),
build_action_listener(Editor::newline_above),
build_action_listener(Editor::newline_below),
build_action_listener(Editor::backspace),
build_action_listener(Editor::delete),
build_action_listener(Editor::tab),
build_action_listener(Editor::tab_prev),
build_action_listener(Editor::indent),
build_action_listener(Editor::outdent),
build_action_listener(Editor::delete_line),
build_action_listener(Editor::join_lines),
build_action_listener(Editor::sort_lines_case_sensitive),
build_action_listener(Editor::sort_lines_case_insensitive),
build_action_listener(Editor::reverse_lines),
build_action_listener(Editor::shuffle_lines),
build_action_listener(Editor::convert_to_upper_case),
build_action_listener(Editor::convert_to_lower_case),
build_action_listener(Editor::convert_to_title_case),
build_action_listener(Editor::convert_to_snake_case),
build_action_listener(Editor::convert_to_kebab_case),
build_action_listener(Editor::convert_to_upper_camel_case),
build_action_listener(Editor::convert_to_lower_camel_case),
build_action_listener(Editor::delete_to_previous_word_start),
build_action_listener(Editor::delete_to_previous_subword_start),
build_action_listener(Editor::delete_to_next_word_end),
build_action_listener(Editor::delete_to_next_subword_end),
build_action_listener(Editor::delete_to_beginning_of_line),
build_action_listener(Editor::delete_to_end_of_line),
build_action_listener(Editor::cut_to_end_of_line),
build_action_listener(Editor::duplicate_line),
build_action_listener(Editor::move_line_up),
build_action_listener(Editor::move_line_down),
build_action_listener(Editor::transpose),
build_action_listener(Editor::cut),
build_action_listener(Editor::copy),
build_action_listener(Editor::paste),
build_action_listener(Editor::undo),
build_action_listener(Editor::redo),
build_action_listener(Editor::move_page_up),
build_action_listener(Editor::move_page_down),
build_action_listener(Editor::next_screen),
build_action_listener(Editor::scroll_cursor_top),
build_action_listener(Editor::scroll_cursor_center),
build_action_listener(Editor::scroll_cursor_bottom),
build_action_listener(|editor, _: &LineDown, cx| {
editor.scroll_screen(&ScrollAmount::Line(1.), cx)
}),
build_action_listener(|editor, _: &LineUp, cx| {
editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
}),
build_action_listener(|editor, _: &HalfPageDown, cx| {
editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
}),
build_action_listener(|editor, _: &HalfPageUp, cx| {
editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
}),
build_action_listener(|editor, _: &PageDown, cx| {
editor.scroll_screen(&ScrollAmount::Page(1.), cx)
}),
build_action_listener(|editor, _: &PageUp, cx| {
editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
}),
build_action_listener(Editor::move_to_previous_word_start),
build_action_listener(Editor::move_to_previous_subword_start),
build_action_listener(Editor::move_to_next_word_end),
build_action_listener(Editor::move_to_next_subword_end),
build_action_listener(Editor::move_to_beginning_of_line),
build_action_listener(Editor::move_to_end_of_line),
build_action_listener(Editor::move_to_start_of_paragraph),
build_action_listener(Editor::move_to_end_of_paragraph),
build_action_listener(Editor::move_to_beginning),
build_action_listener(Editor::move_to_end),
build_action_listener(Editor::select_up),
build_action_listener(Editor::select_down),
build_action_listener(Editor::select_left),
build_action_listener(Editor::select_right),
build_action_listener(Editor::select_to_previous_word_start),
build_action_listener(Editor::select_to_previous_subword_start),
build_action_listener(Editor::select_to_next_word_end),
build_action_listener(Editor::select_to_next_subword_end),
build_action_listener(Editor::select_to_beginning_of_line),
build_action_listener(Editor::select_to_end_of_line),
build_action_listener(Editor::select_to_start_of_paragraph),
build_action_listener(Editor::select_to_end_of_paragraph),
build_action_listener(Editor::select_to_beginning),
build_action_listener(Editor::select_to_end),
build_action_listener(Editor::select_all),
build_action_listener(|editor, action, cx| {
editor.select_all_matches(action, cx).log_err();
}),
build_action_listener(Editor::select_line),
build_action_listener(Editor::split_selection_into_lines),
build_action_listener(Editor::add_selection_above),
build_action_listener(Editor::add_selection_below),
build_action_listener(|editor, action, cx| {
editor.select_next(action, cx).log_err();
}),
build_action_listener(|editor, action, cx| {
editor.select_previous(action, cx).log_err();
}),
build_action_listener(Editor::toggle_comments),
build_action_listener(Editor::select_larger_syntax_node),
build_action_listener(Editor::select_smaller_syntax_node),
build_action_listener(Editor::move_to_enclosing_bracket),
build_action_listener(Editor::undo_selection),
build_action_listener(Editor::redo_selection),
build_action_listener(Editor::go_to_diagnostic),
build_action_listener(Editor::go_to_prev_diagnostic),
build_action_listener(Editor::go_to_hunk),
build_action_listener(Editor::go_to_prev_hunk),
build_action_listener(Editor::go_to_definition),
build_action_listener(Editor::go_to_definition_split),
build_action_listener(Editor::go_to_type_definition),
build_action_listener(Editor::go_to_type_definition_split),
build_action_listener(Editor::fold),
build_action_listener(Editor::fold_at),
build_action_listener(Editor::unfold_lines),
build_action_listener(Editor::unfold_at),
build_action_listener(Editor::fold_selected_ranges),
build_action_listener(Editor::show_completions),
// build_action_listener(Editor::toggle_code_actions), todo!()
// build_action_listener(Editor::open_excerpts), todo!()
build_action_listener(Editor::toggle_soft_wrap),
build_action_listener(Editor::toggle_inlay_hints),
build_action_listener(Editor::reveal_in_finder),
build_action_listener(Editor::copy_path),
build_action_listener(Editor::copy_relative_path),
build_action_listener(Editor::copy_highlight_json),
build_action_listener(|editor, action, cx| {
editor
.format(action, cx)
.map(|task| task.detach_and_log_err(cx));
}),
build_action_listener(Editor::restart_language_server),
build_action_listener(Editor::show_character_palette),
// build_action_listener(Editor::confirm_completion), todo!()
// build_action_listener(Editor::confirm_code_action), todo!()
// build_action_listener(Editor::rename), todo!()
// build_action_listener(Editor::confirm_rename), todo!()
// build_action_listener(Editor::find_all_references), todo!()
build_action_listener(Editor::next_copilot_suggestion),
build_action_listener(Editor::previous_copilot_suggestion),
build_action_listener(Editor::copilot_suggest),
build_key_listener(
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
if phase == DispatchPhase::Bubble {
if let KeyMatch::Some(action) = cx.match_keystroke(
&global_element_id,
&key_down.keystroke,
dispatch_context,
) {
return Some(action);
}
}
None
},
),
]
}
fn build_key_listener<T: 'static>( fn build_key_listener<T: 'static>(
listener: impl Fn( listener: impl Fn(
&mut Editor, &mut Editor,

View file

@ -144,8 +144,7 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
editor.hover_state.info_task = None; editor.hover_state.info_task = None;
editor.hover_state.triggered_from = None; editor.hover_state.triggered_from = None;
// todo!() editor.clear_background_highlights::<HoverState>(cx);
// editor.clear_background_highlights::<HoverState>(cx);
if did_hide { if did_hide {
cx.notify(); cx.notify();
@ -325,23 +324,22 @@ fn show_hover(
}; };
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
todo!(); if let Some(symbol_range) = hover_popover
// if let Some(symbol_range) = hover_popover .as_ref()
// .as_ref() .and_then(|hover_popover| hover_popover.symbol_range.as_text_range())
// .and_then(|hover_popover| hover_popover.symbol_range.as_text_range()) {
// { // Highlight the selected symbol using a background highlight
// // Highlight the selected symbol using a background highlight this.highlight_background::<HoverState>(
// this.highlight_background::<HoverState>( vec![symbol_range],
// vec![symbol_range], |theme| theme.element_hover, // todo! update theme
// |theme| theme.editor.hover_popover.highlight, cx,
// cx, );
// ); } else {
// } else { this.clear_background_highlights::<HoverState>(cx);
// this.clear_background_highlights::<HoverState>(cx); }
// }
// this.hover_state.info_popover = hover_popover;
// this.hover_state.info_popover = hover_popover; cx.notify();
// cx.notify();
})?; })?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())

View file

@ -171,173 +171,170 @@ pub fn update_inlay_link_and_hover_points(
shift_held: bool, shift_held: bool,
cx: &mut ViewContext<'_, Editor>, cx: &mut ViewContext<'_, Editor>,
) { ) {
todo!("old implementation below") let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
} Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
// ) { } else {
// let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 { None
// Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left)) };
// } else { let mut go_to_definition_updated = false;
// None let mut hover_updated = false;
// }; if let Some(hovered_offset) = hovered_offset {
// let mut go_to_definition_updated = false; let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
// let mut hover_updated = false; let previous_valid_anchor = buffer_snapshot.anchor_at(
// if let Some(hovered_offset) = hovered_offset { point_for_position.previous_valid.to_point(snapshot),
// let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); Bias::Left,
// let previous_valid_anchor = buffer_snapshot.anchor_at( );
// point_for_position.previous_valid.to_point(snapshot), let next_valid_anchor = buffer_snapshot.anchor_at(
// Bias::Left, point_for_position.next_valid.to_point(snapshot),
// ); Bias::Right,
// let next_valid_anchor = buffer_snapshot.anchor_at( );
// point_for_position.next_valid.to_point(snapshot), if let Some(hovered_hint) = editor
// Bias::Right, .visible_inlay_hints(cx)
// ); .into_iter()
// if let Some(hovered_hint) = editor .skip_while(|hint| {
// .visible_inlay_hints(cx) hint.position
// .into_iter() .cmp(&previous_valid_anchor, &buffer_snapshot)
// .skip_while(|hint| { .is_lt()
// hint.position })
// .cmp(&previous_valid_anchor, &buffer_snapshot) .take_while(|hint| {
// .is_lt() hint.position
// }) .cmp(&next_valid_anchor, &buffer_snapshot)
// .take_while(|hint| { .is_le()
// hint.position })
// .cmp(&next_valid_anchor, &buffer_snapshot) .max_by_key(|hint| hint.id)
// .is_le() {
// }) let inlay_hint_cache = editor.inlay_hint_cache();
// .max_by_key(|hint| hint.id) let excerpt_id = previous_valid_anchor.excerpt_id;
// { if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
// let inlay_hint_cache = editor.inlay_hint_cache(); match cached_hint.resolve_state {
// let excerpt_id = previous_valid_anchor.excerpt_id; ResolveState::CanResolve(_, _) => {
// if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) { if let Some(buffer_id) = previous_valid_anchor.buffer_id {
// match cached_hint.resolve_state { inlay_hint_cache.spawn_hint_resolve(
// ResolveState::CanResolve(_, _) => { buffer_id,
// if let Some(buffer_id) = previous_valid_anchor.buffer_id { excerpt_id,
// inlay_hint_cache.spawn_hint_resolve( hovered_hint.id,
// buffer_id, cx,
// excerpt_id, );
// hovered_hint.id, }
// cx, }
// ); ResolveState::Resolved => {
// } let mut extra_shift_left = 0;
// } let mut extra_shift_right = 0;
// ResolveState::Resolved => { if cached_hint.padding_left {
// let mut extra_shift_left = 0; extra_shift_left += 1;
// let mut extra_shift_right = 0; extra_shift_right += 1;
// if cached_hint.padding_left { }
// extra_shift_left += 1; if cached_hint.padding_right {
// extra_shift_right += 1; extra_shift_right += 1;
// } }
// if cached_hint.padding_right { match cached_hint.label {
// extra_shift_right += 1; project::InlayHintLabel::String(_) => {
// } if let Some(tooltip) = cached_hint.tooltip {
// match cached_hint.label { hover_popover::hover_at_inlay(
// project::InlayHintLabel::String(_) => { editor,
// if let Some(tooltip) = cached_hint.tooltip { InlayHover {
// hover_popover::hover_at_inlay( excerpt: excerpt_id,
// editor, tooltip: match tooltip {
// InlayHover { InlayHintTooltip::String(text) => HoverBlock {
// excerpt: excerpt_id, text,
// tooltip: match tooltip { kind: HoverBlockKind::PlainText,
// InlayHintTooltip::String(text) => HoverBlock { },
// text, InlayHintTooltip::MarkupContent(content) => {
// kind: HoverBlockKind::PlainText, HoverBlock {
// }, text: content.value,
// InlayHintTooltip::MarkupContent(content) => { kind: content.kind,
// HoverBlock { }
// text: content.value, }
// kind: content.kind, },
// } range: InlayHighlight {
// } inlay: hovered_hint.id,
// }, inlay_position: hovered_hint.position,
// range: InlayHighlight { range: extra_shift_left
// inlay: hovered_hint.id, ..hovered_hint.text.len() + extra_shift_right,
// inlay_position: hovered_hint.position, },
// range: extra_shift_left },
// ..hovered_hint.text.len() + extra_shift_right, cx,
// }, );
// }, hover_updated = true;
// cx, }
// ); }
// hover_updated = true; project::InlayHintLabel::LabelParts(label_parts) => {
// } let hint_start =
// } snapshot.anchor_to_inlay_offset(hovered_hint.position);
// project::InlayHintLabel::LabelParts(label_parts) => { if let Some((hovered_hint_part, part_range)) =
// let hint_start = hover_popover::find_hovered_hint_part(
// snapshot.anchor_to_inlay_offset(hovered_hint.position); label_parts,
// if let Some((hovered_hint_part, part_range)) = hint_start,
// hover_popover::find_hovered_hint_part( hovered_offset,
// label_parts, )
// hint_start, {
// hovered_offset, let highlight_start =
// ) (part_range.start - hint_start).0 + extra_shift_left;
// { let highlight_end =
// let highlight_start = (part_range.end - hint_start).0 + extra_shift_right;
// (part_range.start - hint_start).0 + extra_shift_left; let highlight = InlayHighlight {
// let highlight_end = inlay: hovered_hint.id,
// (part_range.end - hint_start).0 + extra_shift_right; inlay_position: hovered_hint.position,
// let highlight = InlayHighlight { range: highlight_start..highlight_end,
// inlay: hovered_hint.id, };
// inlay_position: hovered_hint.position, if let Some(tooltip) = hovered_hint_part.tooltip {
// range: highlight_start..highlight_end, hover_popover::hover_at_inlay(
// }; editor,
// if let Some(tooltip) = hovered_hint_part.tooltip { InlayHover {
// hover_popover::hover_at_inlay( excerpt: excerpt_id,
// editor, tooltip: match tooltip {
// InlayHover { InlayHintLabelPartTooltip::String(text) => {
// excerpt: excerpt_id, HoverBlock {
// tooltip: match tooltip { text,
// InlayHintLabelPartTooltip::String(text) => { kind: HoverBlockKind::PlainText,
// HoverBlock { }
// text, }
// kind: HoverBlockKind::PlainText, InlayHintLabelPartTooltip::MarkupContent(
// } content,
// } ) => HoverBlock {
// InlayHintLabelPartTooltip::MarkupContent( text: content.value,
// content, kind: content.kind,
// ) => HoverBlock { },
// text: content.value, },
// kind: content.kind, range: highlight.clone(),
// }, },
// }, cx,
// range: highlight.clone(), );
// }, hover_updated = true;
// cx, }
// ); if let Some((language_server_id, location)) =
// hover_updated = true; hovered_hint_part.location
// } {
// if let Some((language_server_id, location)) = go_to_definition_updated = true;
// hovered_hint_part.location update_go_to_definition_link(
// { editor,
// go_to_definition_updated = true; Some(GoToDefinitionTrigger::InlayHint(
// update_go_to_definition_link( highlight,
// editor, location,
// Some(GoToDefinitionTrigger::InlayHint( language_server_id,
// highlight, )),
// location, cmd_held,
// language_server_id, shift_held,
// )), cx,
// cmd_held, );
// shift_held, }
// cx, }
// ); }
// } };
// } }
// } ResolveState::Resolving => {}
// }; }
// } }
// ResolveState::Resolving => {} }
// } }
// }
// }
// }
// if !go_to_definition_updated { if !go_to_definition_updated {
// update_go_to_definition_link(editor, None, cmd_held, shift_held, cx); update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
// } }
// if !hover_updated { if !hover_updated {
// hover_popover::hover_at(editor, None, cx); hover_popover::hover_at(editor, None, cx);
// } }
// } }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinkDefinitionKind { pub enum LinkDefinitionKind {

View file

@ -288,16 +288,15 @@ impl ScrollManager {
} }
} }
// todo!()
impl Editor { impl Editor {
// pub fn vertical_scroll_margin(&mut self) -> usize { pub fn vertical_scroll_margin(&mut self) -> usize {
// self.scroll_manager.vertical_scroll_margin as usize self.scroll_manager.vertical_scroll_margin as usize
// } }
// pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) { pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
// self.scroll_manager.vertical_scroll_margin = margin_rows as f32; self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
// cx.notify(); cx.notify();
// } }
pub fn visible_line_count(&self) -> Option<f32> { pub fn visible_line_count(&self) -> Option<f32> {
self.scroll_manager.visible_line_count self.scroll_manager.visible_line_count
@ -349,11 +348,9 @@ impl Editor {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
} }
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<Pixels> { pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
// todo!() Should `self.scroll_manager.anchor.scroll_position()` return `Pixels`? self.scroll_manager.anchor.scroll_position(&display_map)
// self.scroll_manager.anchor.scroll_position(&display_map)
todo!()
} }
pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) { pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
@ -382,50 +379,50 @@ impl Editor {
.set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx); .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
} }
// pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) { pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
// if matches!(self.mode, EditorMode::SingleLine) { if matches!(self.mode, EditorMode::SingleLine) {
// cx.propagate_action(); cx.propagate();
// return; return;
// } }
// if self.take_rename(true, cx).is_some() { if self.take_rename(true, cx).is_some() {
// return; return;
// } }
// let cur_position = self.scroll_position(cx); let cur_position = self.scroll_position(cx);
// let new_pos = cur_position + point(0., amount.lines(self)); let new_pos = cur_position + point(0., amount.lines(self));
// self.set_scroll_position(new_pos, cx); self.set_scroll_position(new_pos, cx);
// } }
// /// Returns an ordering. The newest selection is: /// Returns an ordering. The newest selection is:
// /// Ordering::Equal => on screen /// Ordering::Equal => on screen
// /// Ordering::Less => above the screen /// Ordering::Less => above the screen
// /// Ordering::Greater => below the screen /// Ordering::Greater => below the screen
// pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering { pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
// let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
// let newest_head = self let newest_head = self
// .selections .selections
// .newest_anchor() .newest_anchor()
// .head() .head()
// .to_display_point(&snapshot); .to_display_point(&snapshot);
// let screen_top = self let screen_top = self
// .scroll_manager .scroll_manager
// .anchor .anchor
// .anchor .anchor
// .to_display_point(&snapshot); .to_display_point(&snapshot);
// if screen_top > newest_head { if screen_top > newest_head {
// return Ordering::Less; return Ordering::Less;
// } }
// if let Some(visible_lines) = self.visible_line_count() { if let Some(visible_lines) = self.visible_line_count() {
// if newest_head.row() < screen_top.row() + visible_lines as u32 { if newest_head.row() < screen_top.row() + visible_lines as u32 {
// return Ordering::Equal; return Ordering::Equal;
// } }
// } }
// Ordering::Greater Ordering::Greater
// } }
pub fn read_scroll_position_from_db( pub fn read_scroll_position_from_db(
&mut self, &mut self,

View file

@ -1,66 +1,27 @@
use super::Axis; use super::Axis;
use crate::Editor; use crate::{
use gpui::{AppContext, Point, ViewContext}; Autoscroll, Bias, Editor, EditorMode, NextScreen, ScrollAnchor, ScrollCursorBottom,
ScrollCursorCenter, ScrollCursorTop,
// actions!( };
// editor, use gpui::{actions, AppContext, Point, ViewContext};
// [
// LineDown,
// LineUp,
// HalfPageDown,
// HalfPageUp,
// PageDown,
// PageUp,
// NextScreen,
// ScrollCursorTop,
// ScrollCursorCenter,
// ScrollCursorBottom,
// ]
// );
pub fn init(cx: &mut AppContext) {
// todo!()
// cx.add_action(Editor::next_screen);
// cx.add_action(Editor::scroll_cursor_top);
// cx.add_action(Editor::scroll_cursor_center);
// cx.add_action(Editor::scroll_cursor_bottom);
// cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
// this.scroll_screen(&ScrollAmount::Line(1.), cx)
// });
// cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
// this.scroll_screen(&ScrollAmount::Line(-1.), cx)
// });
// cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
// this.scroll_screen(&ScrollAmount::Page(0.5), cx)
// });
// cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
// this.scroll_screen(&ScrollAmount::Page(-0.5), cx)
// });
// cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
// this.scroll_screen(&ScrollAmount::Page(1.), cx)
// });
// cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
// this.scroll_screen(&ScrollAmount::Page(-1.), cx)
// });
}
impl Editor { impl Editor {
// pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) -> Option<()> { pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
// if self.take_rename(true, cx).is_some() { if self.take_rename(true, cx).is_some() {
// return None; return;
// } }
// todo!()
// if self.mouse_context_menu.read(cx).visible() { // if self.mouse_context_menu.read(cx).visible() {
// return None; // return None;
// } // }
// if matches!(self.mode, EditorMode::SingleLine) { if matches!(self.mode, EditorMode::SingleLine) {
// cx.propagate_action(); cx.propagate();
// return None; return;
// } }
// self.request_autoscroll(Autoscroll::Next, cx); self.request_autoscroll(Autoscroll::Next, cx);
// Some(()) }
// }
pub fn scroll( pub fn scroll(
&mut self, &mut self,
@ -72,79 +33,71 @@ impl Editor {
self.set_scroll_position(scroll_position, cx); self.set_scroll_position(scroll_position, cx);
} }
// fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) { pub fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
// let snapshot = editor.snapshot(cx).display_snapshot; let snapshot = self.snapshot(cx).display_snapshot;
// let scroll_margin_rows = editor.vertical_scroll_margin() as u32; let scroll_margin_rows = self.vertical_scroll_margin() as u32;
// let mut new_screen_top = editor.selections.newest_display(cx).head(); let mut new_screen_top = self.selections.newest_display(cx).head();
// *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows); *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
// *new_screen_top.column_mut() = 0; *new_screen_top.column_mut() = 0;
// let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
// let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
// editor.set_scroll_anchor( self.set_scroll_anchor(
// ScrollAnchor { ScrollAnchor {
// anchor: new_anchor, anchor: new_anchor,
// offset: Default::default(), offset: Default::default(),
// }, },
// cx, cx,
// ) )
// } }
// fn scroll_cursor_center( pub fn scroll_cursor_center(&mut self, _: &ScrollCursorCenter, cx: &mut ViewContext<Editor>) {
// editor: &mut Editor, let snapshot = self.snapshot(cx).display_snapshot;
// _: &ScrollCursorCenter, let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
// cx: &mut ViewContext<Editor>, visible_rows as u32
// ) { } else {
// let snapshot = editor.snapshot(cx).display_snapshot; return;
// let visible_rows = if let Some(visible_rows) = editor.visible_line_count() { };
// visible_rows as u32
// } else {
// return;
// };
// let mut new_screen_top = editor.selections.newest_display(cx).head(); let mut new_screen_top = self.selections.newest_display(cx).head();
// *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2); *new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
// *new_screen_top.column_mut() = 0; *new_screen_top.column_mut() = 0;
// let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
// let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
// editor.set_scroll_anchor( self.set_scroll_anchor(
// ScrollAnchor { ScrollAnchor {
// anchor: new_anchor, anchor: new_anchor,
// offset: Default::default(), offset: Default::default(),
// }, },
// cx, cx,
// ) )
// } }
// fn scroll_cursor_bottom( pub fn scroll_cursor_bottom(&mut self, _: &ScrollCursorBottom, cx: &mut ViewContext<Editor>) {
// editor: &mut Editor, let snapshot = self.snapshot(cx).display_snapshot;
// _: &ScrollCursorBottom, let scroll_margin_rows = self.vertical_scroll_margin() as u32;
// cx: &mut ViewContext<Editor>, let visible_rows = if let Some(visible_rows) = self.visible_line_count() {
// ) { visible_rows as u32
// let snapshot = editor.snapshot(cx).display_snapshot; } else {
// let scroll_margin_rows = editor.vertical_scroll_margin() as u32; return;
// let visible_rows = if let Some(visible_rows) = editor.visible_line_count() { };
// visible_rows as u32
// } else {
// return;
// };
// let mut new_screen_top = editor.selections.newest_display(cx).head(); let mut new_screen_top = self.selections.newest_display(cx).head();
// *new_screen_top.row_mut() = new_screen_top *new_screen_top.row_mut() = new_screen_top
// .row() .row()
// .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows)); .saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
// *new_screen_top.column_mut() = 0; *new_screen_top.column_mut() = 0;
// let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left); let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
// let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top); let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
// editor.set_scroll_anchor( self.set_scroll_anchor(
// ScrollAnchor { ScrollAnchor {
// anchor: new_anchor, anchor: new_anchor,
// offset: Default::default(), offset: Default::default(),
// }, },
// cx, cx,
// ) )
// } }
} }

View file

@ -302,39 +302,39 @@ impl SelectionsCollection {
.collect() .collect()
} }
// pub fn build_columnar_selection( pub fn build_columnar_selection(
// &mut self, &mut self,
// display_map: &DisplaySnapshot, display_map: &DisplaySnapshot,
// row: u32, row: u32,
// positions: &Range<Pixels>, positions: &Range<Pixels>,
// reversed: bool, reversed: bool,
// text_layout_details: &TextLayoutDetails, text_layout_details: &TextLayoutDetails,
// ) -> Option<Selection<Point>> { ) -> Option<Selection<Point>> {
// let is_empty = positions.start == positions.end; let is_empty = positions.start == positions.end;
// let line_len = display_map.line_len(row); let line_len = display_map.line_len(row);
// let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details); let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
// let start_col = layed_out_line.closest_index_for_x(positions.start) as u32; let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
// if start_col < line_len || (is_empty && positions.start == layed_out_line.width()) { if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
// let start = DisplayPoint::new(row, start_col); let start = DisplayPoint::new(row, start_col);
// let end_col = layed_out_line.closest_index_for_x(positions.end) as u32; let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
// let end = DisplayPoint::new(row, end_col); let end = DisplayPoint::new(row, end_col);
// Some(Selection { Some(Selection {
// id: post_inc(&mut self.next_selection_id), id: post_inc(&mut self.next_selection_id),
// start: start.to_point(display_map), start: start.to_point(display_map),
// end: end.to_point(display_map), end: end.to_point(display_map),
// reversed, reversed,
// goal: SelectionGoal::HorizontalRange { goal: SelectionGoal::HorizontalRange {
// start: positions.start, start: positions.start.into(),
// end: positions.end, end: positions.end.into(),
// }, },
// }) })
// } else { } else {
// None None
// } }
// } }
pub(crate) fn change_with<R>( pub(crate) fn change_with<R>(
&mut self, &mut self,

View file

@ -12,7 +12,6 @@ use workspace::ModalRegistry;
actions!(Toggle, Cancel, Confirm); actions!(Toggle, Cancel, Confirm);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
cx.register_action_type::<Toggle>();
cx.global_mut::<ModalRegistry>() cx.global_mut::<ModalRegistry>()
.register_modal(Toggle, |workspace, cx| { .register_modal(Toggle, |workspace, cx| {
let editor = workspace let editor = workspace

View file

@ -1,9 +1,54 @@
use crate::SharedString; use crate::SharedString;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize; use serde::Deserialize;
use std::any::{type_name, Any}; use std::any::{type_name, Any};
/// Actions are used to implement keyboard-driven UI.
/// When you declare an action, you can bind keys to the action in the keymap and
/// listeners for that action in the element tree.
///
/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
/// action for each listed action name.
/// ```rust
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
/// ```
/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
/// it will automatically
/// ```
/// #[action]
/// pub struct SelectNext {
/// pub replace_newest: bool,
/// }
///
/// Any type A that satisfies the following bounds is automatically an action:
///
/// ```
/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
/// ```
///
/// The `#[action]` annotation will derive these implementations for your struct automatically. If you
/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only
/// generates the code needed to register your action before `main`. Then you'll need to implement all
/// the traits manually.
///
/// ```
/// #[gpui::register_action]
/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
/// pub struct Paste {
/// pub content: SharedString,
/// }
///
/// impl std::default::Default for Paste {
/// fn default() -> Self {
/// Self {
/// content: SharedString::from("🍝"),
/// }
/// }
/// }
/// ```
pub trait Action: std::fmt::Debug + 'static { pub trait Action: std::fmt::Debug + 'static {
fn qualified_name() -> SharedString fn qualified_name() -> SharedString
where where
@ -17,32 +62,7 @@ pub trait Action: std::fmt::Debug + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
} }
// actions defines structs that can be used as actions. // Types become actions by satisfying a list of trait bounds.
#[macro_export]
macro_rules! actions {
() => {};
( $name:ident ) => {
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name;
};
( $name:ident { $($token:tt)* } ) => {
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name { $($token)* }
};
( $name:ident, $($rest:tt)* ) => {
actions!($name);
actions!($($rest)*);
};
( $name:ident { $($token:tt)* }, $($rest:tt)* ) => {
actions!($name { $($token)* });
actions!($($rest)*);
};
}
impl<A> Action for A impl<A> Action for A
where where
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static, A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
@ -80,6 +100,61 @@ where
} }
} }
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
lazy_static! {
static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
}
#[derive(Default)]
struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>,
all_names: Vec<SharedString>, // So we can return a static slice.
}
/// Register an action type to allow it to be referenced in keymaps.
pub fn register_action<A: Action>() {
let name = A::qualified_name();
let mut lock = ACTION_REGISTRY.write();
lock.builders_by_name.insert(name.clone(), A::build);
lock.all_names.push(name);
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
let lock = ACTION_REGISTRY.read();
let build_action = lock
.builders_by_name
.get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
(build_action)(params)
}
pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
let lock = ACTION_REGISTRY.read();
RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
registry.all_names.as_slice()
})
}
/// Defines unit structs that can be used as actions.
/// To use more complex data types as actions, annotate your type with the #[action] macro.
#[macro_export]
macro_rules! actions {
() => {};
( $name:ident ) => {
#[gpui::register_action]
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name;
};
( $name:ident, $($rest:tt)* ) => {
actions!($name);
actions!($($rest)*);
};
}
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DispatchContext { pub struct DispatchContext {
set: HashSet<SharedString>, set: HashSet<SharedString>,
@ -317,22 +392,23 @@ fn skip_whitespace(source: &str) -> &str {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate as gpui;
use DispatchContextPredicate::*; use DispatchContextPredicate::*;
#[test] #[test]
fn test_actions_definition() { fn test_actions_definition() {
{ {
actions!(A, B { field: i32 }, C, D, E, F {}, G); actions!(A, B, C, D, E, F, G);
} }
{ {
actions!( actions!(
A, A,
B { field: i32 }, B,
C, C,
D, D,
E, E,
F {}, F,
G, // Don't wrap, test the trailing comma G, // Don't wrap, test the trailing comma
); );
} }

View file

@ -17,9 +17,9 @@ use crate::{
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
View, Window, WindowContext, WindowHandle, WindowId, WindowContext, WindowHandle, WindowId,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque}; use collections::{HashMap, HashSet, VecDeque};
@ -140,7 +140,6 @@ impl App {
} }
} }
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>; pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>; type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>; type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
@ -154,7 +153,7 @@ type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
// } // }
pub struct AppContext { pub struct AppContext {
this: Weak<AppCell>, pub(crate) this: Weak<AppCell>,
pub(crate) platform: Rc<dyn Platform>, pub(crate) platform: Rc<dyn Platform>,
app_metadata: AppMetadata, app_metadata: AppMetadata,
text_system: Arc<TextSystem>, text_system: Arc<TextSystem>,
@ -176,7 +175,6 @@ pub struct AppContext {
pub(crate) keymap: Arc<Mutex<Keymap>>, pub(crate) keymap: Arc<Mutex<Keymap>>,
pub(crate) global_action_listeners: pub(crate) global_action_listeners:
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>, HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
action_builders: HashMap<SharedString, ActionBuilder>,
pending_effects: VecDeque<Effect>, pending_effects: VecDeque<Effect>,
pub(crate) pending_notifications: HashSet<EntityId>, pub(crate) pending_notifications: HashSet<EntityId>,
pub(crate) pending_global_notifications: HashSet<TypeId>, pub(crate) pending_global_notifications: HashSet<TypeId>,
@ -234,7 +232,6 @@ impl AppContext {
windows: SlotMap::with_key(), windows: SlotMap::with_key(),
keymap: Arc::new(Mutex::new(Keymap::default())), keymap: Arc::new(Mutex::new(Keymap::default())),
global_action_listeners: HashMap::default(), global_action_listeners: HashMap::default(),
action_builders: HashMap::default(),
pending_effects: VecDeque::new(), pending_effects: VecDeque::new(),
pending_notifications: HashSet::default(), pending_notifications: HashSet::default(),
pending_global_notifications: HashSet::default(), pending_global_notifications: HashSet::default(),
@ -522,7 +519,7 @@ impl AppContext {
window_handle window_handle
.update(self, |_, cx| { .update(self, |_, cx| {
if cx.window.focus == focused { if cx.window.focus == focused {
let mut listeners = mem::take(&mut cx.window.focus_listeners); let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners);
let focused = focused let focused = focused
.map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap());
let blurred = cx let blurred = cx
@ -538,8 +535,8 @@ impl AppContext {
} }
} }
listeners.extend(cx.window.focus_listeners.drain(..)); listeners.extend(cx.window.current_frame.focus_listeners.drain(..));
cx.window.focus_listeners = listeners; cx.window.current_frame.focus_listeners = listeners;
} }
}) })
.ok(); .ok();
@ -695,10 +692,6 @@ impl AppContext {
) )
} }
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
self.action_builders.keys().cloned()
}
/// Move the global of the given type to the stack. /// Move the global of the given type to the stack.
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> { pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
GlobalLease::new( GlobalLease::new(
@ -761,24 +754,6 @@ impl AppContext {
})); }));
} }
/// Register an action type to allow it to be referenced in keymaps.
pub fn register_action_type<A: Action>(&mut self) {
self.action_builders.insert(A::qualified_name(), A::build);
}
/// Construct an action based on its name and parameters.
pub fn build_action(
&mut self,
name: &str,
params: Option<serde_json::Value>,
) -> Result<Box<dyn Action>> {
let build = self
.action_builders
.get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
(build)(params)
}
/// Event handlers propagate events by default. Call this method to stop dispatching to /// Event handlers propagate events by default. Call this method to stop dispatching to
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
/// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by

View file

@ -13,6 +13,7 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst}, atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak, Arc, Weak,
}, },
thread::panicking,
}; };
slotmap::new_key_type! { pub struct EntityId; } slotmap::new_key_type! { pub struct EntityId; }
@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
impl<'a, T> Drop for Lease<'a, T> { impl<'a, T> Drop for Lease<'a, T> {
fn drop(&mut self) { fn drop(&mut self) {
if self.entity.is_some() { if self.entity.is_some() && !panicking() {
// We don't panic here, because other panics can cause us to drop the lease without ending it cleanly. panic!("Leases must be ended with EntityMap::end_lease")
log::error!("Leases must be ended with EntityMap::end_lease")
} }
} }
} }

View file

@ -203,6 +203,15 @@ pub fn red() -> Hsla {
} }
} }
pub fn blue() -> Hsla {
Hsla {
h: 0.6,
s: 1.,
l: 0.5,
a: 1.,
}
}
impl Hsla { impl Hsla {
/// Returns true if the HSLA color is fully transparent, false otherwise. /// Returns true if the HSLA color is fully transparent, false otherwise.
pub fn is_transparent(&self) -> bool { pub fn is_transparent(&self) -> bool {

View file

@ -2,8 +2,10 @@ mod div;
mod img; mod img;
mod svg; mod svg;
mod text; mod text;
mod uniform_list;
pub use div::*; pub use div::*;
pub use img::*; pub use img::*;
pub use svg::*; pub use svg::*;
pub use text::*; pub use text::*;
pub use uniform_list::*;

View file

@ -1,28 +1,28 @@
use crate::{ use crate::{
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId, point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement, GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
}; };
use refineable::Refineable; use refineable::Refineable;
use smallvec::SmallVec; use smallvec::SmallVec;
pub struct Div< pub struct Div<
V: 'static, V: 'static,
I: ElementInteraction<V> = StatelessInteraction<V>, I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled, F: ElementFocus<V> = FocusDisabled,
> { > {
interaction: I, interactivity: I,
focus: F, focus: F,
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>, group: Option<SharedString>,
base_style: StyleRefinement, base_style: StyleRefinement,
} }
pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> { pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
Div { Div {
interaction: StatelessInteraction::default(), interactivity: StatelessInteractivity::default(),
focus: FocusDisabled, focus: FocusDisabled,
children: SmallVec::new(), children: SmallVec::new(),
group: None, group: None,
@ -30,14 +30,14 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
} }
} }
impl<V, F> Div<V, StatelessInteraction<V>, F> impl<V, F> Div<V, StatelessInteractivity<V>, F>
where where
V: 'static, V: 'static,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteraction<V>, F> { pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
Div { Div {
interaction: id.into().into(), interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
focus: self.focus, focus: self.focus,
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -48,7 +48,7 @@ where
impl<V, I, F> Div<V, I, F> impl<V, I, F> Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn group(mut self, group: impl Into<SharedString>) -> Self { pub fn group(mut self, group: impl Into<SharedString>) -> Self {
@ -98,16 +98,20 @@ where
let mut computed_style = Style::default(); let mut computed_style = Style::default();
computed_style.refine(&self.base_style); computed_style.refine(&self.base_style);
self.focus.refine_style(&mut computed_style, cx); self.focus.refine_style(&mut computed_style, cx);
self.interaction self.interactivity.refine_style(
.refine_style(&mut computed_style, bounds, &element_state.interactive, cx); &mut computed_style,
bounds,
&element_state.interactive,
cx,
);
computed_style computed_style
} }
} }
impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> { impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
pub fn focusable(self) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> { pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div { Div {
interaction: self.interaction, interactivity: self.interactivity,
focus: FocusEnabled::new(), focus: FocusEnabled::new(),
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -118,9 +122,9 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
pub fn track_focus( pub fn track_focus(
self, self,
handle: &FocusHandle, handle: &FocusHandle,
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> { ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div { Div {
interaction: self.interaction, interactivity: self.interactivity,
focus: FocusEnabled::tracked(handle), focus: FocusEnabled::tracked(handle),
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -145,13 +149,13 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
} }
} }
impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> { impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
pub fn track_focus( pub fn track_focus(
self, self,
handle: &FocusHandle, handle: &FocusHandle,
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> { ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div { Div {
interaction: self.interaction.into_stateful(handle), interactivity: self.interactivity.into_stateful(handle),
focus: handle.clone().into(), focus: handle.clone().into(),
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -163,7 +167,7 @@ impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>> impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
where where
V: 'static, V: 'static,
I: ElementInteraction<V>, I: ElementInteractivity<V>,
{ {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> { fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
&mut self.focus.focus_listeners &mut self.focus.focus_listeners
@ -191,13 +195,13 @@ pub struct DivState {
impl<V, I, F> Element<V> for Div<V, I, F> impl<V, I, F> Element<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
type ElementState = DivState; type ElementState = DivState;
fn id(&self) -> Option<ElementId> { fn id(&self) -> Option<ElementId> {
self.interaction self.interactivity
.as_stateful() .as_stateful()
.map(|identified| identified.id.clone()) .map(|identified| identified.id.clone())
} }
@ -209,7 +213,7 @@ where
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) -> Self::ElementState { ) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default(); let mut element_state = element_state.unwrap_or_default();
self.interaction.initialize(cx, |cx| { self.interactivity.initialize(cx, |cx| {
self.focus self.focus
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| { .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
element_state.focus_handle = focus_handle; element_state.focus_handle = focus_handle;
@ -281,11 +285,11 @@ where
(child_max - child_min).into() (child_max - child_min).into()
}; };
cx.stack(z_index, |cx| { cx.with_z_index(z_index, |cx| {
cx.stack(0, |cx| { cx.with_z_index(0, |cx| {
style.paint(bounds, cx); style.paint(bounds, cx);
this.focus.paint(bounds, cx); this.focus.paint(bounds, cx);
this.interaction.paint( this.interactivity.paint(
bounds, bounds,
content_size, content_size,
style.overflow, style.overflow,
@ -293,7 +297,7 @@ where
cx, cx,
); );
}); });
cx.stack(1, |cx| { cx.with_z_index(1, |cx| {
style.apply_text_style(cx, |cx| { style.apply_text_style(cx, |cx| {
style.apply_overflow(bounds, cx, |cx| { style.apply_overflow(bounds, cx, |cx| {
let scroll_offset = element_state.interactive.scroll_offset(); let scroll_offset = element_state.interactive.scroll_offset();
@ -316,7 +320,7 @@ where
impl<V, I, F> Component<V> for Div<V, I, F> impl<V, I, F> Component<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
@ -326,7 +330,7 @@ where
impl<V, I, F> ParentElement<V> for Div<V, I, F> impl<V, I, F> ParentElement<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
@ -336,7 +340,7 @@ where
impl<V, I, F> Styled for Div<V, I, F> impl<V, I, F> Styled for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
@ -346,19 +350,19 @@ where
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F> impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interaction.as_stateless_mut() self.interactivity.as_stateless_mut()
} }
} }
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteraction<V>, F> impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interaction &mut self.interactivity
} }
} }

View file

@ -1,15 +1,15 @@
use crate::{ use crate::{
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus, div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StyleRefinement, Styled, ViewContext, StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
}; };
use futures::FutureExt; use futures::FutureExt;
use util::ResultExt; use util::ResultExt;
pub struct Img< pub struct Img<
V: 'static, V: 'static,
I: ElementInteraction<V> = StatelessInteraction<V>, I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled, F: ElementFocus<V> = FocusDisabled,
> { > {
base: Div<V, I, F>, base: Div<V, I, F>,
@ -17,7 +17,7 @@ pub struct Img<
grayscale: bool, grayscale: bool,
} }
pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> { pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
Img { Img {
base: div(), base: div(),
uri: None, uri: None,
@ -28,7 +28,7 @@ pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I, F> Img<V, I, F> impl<V, I, F> Img<V, I, F>
where where
V: 'static, V: 'static,
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self { pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
@ -42,11 +42,11 @@ where
} }
} }
impl<V, F> Img<V, StatelessInteraction<V>, F> impl<V, F> Img<V, StatelessInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteraction<V>, F> { pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
Img { Img {
base: self.base.id(id), base: self.base.id(id),
uri: self.uri, uri: self.uri,
@ -57,7 +57,7 @@ where
impl<V, I, F> Component<V> for Img<V, I, F> impl<V, I, F> Component<V> for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
@ -67,7 +67,7 @@ where
impl<V, I, F> Element<V> for Img<V, I, F> impl<V, I, F> Element<V> for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
type ElementState = DivState; type ElementState = DivState;
@ -101,7 +101,7 @@ where
element_state: &mut Self::ElementState, element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) { ) {
cx.stack(0, |cx| { cx.with_z_index(0, |cx| {
self.base.paint(bounds, view, element_state, cx); self.base.paint(bounds, view, element_state, cx);
}); });
@ -118,7 +118,7 @@ where
.and_then(ResultExt::log_err) .and_then(ResultExt::log_err)
{ {
let corner_radii = 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.with_z_index(1, |cx| {
cx.paint_image(bounds, corner_radii, data, self.grayscale) cx.paint_image(bounds, corner_radii, data, self.grayscale)
.log_err() .log_err()
}); });
@ -136,7 +136,7 @@ where
impl<V, I, F> Styled for Img<V, I, F> impl<V, I, F> Styled for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
@ -146,27 +146,27 @@ where
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F> impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interaction() self.base.stateless_interactivity()
} }
} }
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteraction<V>, F> impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interaction() self.base.stateful_interactivity()
} }
} }
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>> impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
where where
V: 'static, V: 'static,
I: ElementInteraction<V>, I: ElementInteractivity<V>,
{ {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> { fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners() self.base.focus_listeners()

View file

@ -1,21 +1,21 @@
use crate::{ use crate::{
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId, div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
StatelessInteractive, StyleRefinement, Styled, ViewContext, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
}; };
use util::ResultExt; use util::ResultExt;
pub struct Svg< pub struct Svg<
V: 'static, V: 'static,
I: ElementInteraction<V> = StatelessInteraction<V>, I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled, F: ElementFocus<V> = FocusDisabled,
> { > {
base: Div<V, I, F>, base: Div<V, I, F>,
path: Option<SharedString>, path: Option<SharedString>,
} }
pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> { pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
Svg { Svg {
base: div(), base: div(),
path: None, path: None,
@ -24,7 +24,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I, F> Svg<V, I, F> impl<V, I, F> Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn path(mut self, path: impl Into<SharedString>) -> Self { pub fn path(mut self, path: impl Into<SharedString>) -> Self {
@ -33,11 +33,11 @@ where
} }
} }
impl<V, F> Svg<V, StatelessInteraction<V>, F> impl<V, F> Svg<V, StatelessInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteraction<V>, F> { pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
Svg { Svg {
base: self.base.id(id), base: self.base.id(id),
path: self.path, path: self.path,
@ -47,7 +47,7 @@ where
impl<V, I, F> Component<V> for Svg<V, I, F> impl<V, I, F> Component<V> for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
@ -57,7 +57,7 @@ where
impl<V, I, F> Element<V> for Svg<V, I, F> impl<V, I, F> Element<V> for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
type ElementState = DivState; type ElementState = DivState;
@ -107,7 +107,7 @@ where
impl<V, I, F> Styled for Svg<V, I, F> impl<V, I, F> Styled for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
@ -117,27 +117,27 @@ where
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F> impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interaction() self.base.stateless_interactivity()
} }
} }
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteraction<V>, F> impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
where where
V: 'static, V: 'static,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interaction() self.base.stateful_interactivity()
} }
} }
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>> impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
{ {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> { fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners() self.base.focus_listeners()

View file

@ -127,6 +127,7 @@ impl<V: 'static> Element<V> for Text<V> {
let element_state = element_state let element_state = element_state
.as_ref() .as_ref()
.expect("measurement has not been performed"); .expect("measurement has not been performed");
let line_height = element_state.line_height; let line_height = element_state.line_height;
let mut line_origin = bounds.origin; let mut line_origin = bounds.origin;
for line in &element_state.lines { for line in &element_state.lines {

View file

@ -0,0 +1,244 @@
use crate::{
point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
StyleRefinement, Styled, ViewContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{cmp, ops::Range, sync::Arc};
use taffy::style::Overflow;
pub fn uniform_list<Id, V, C>(
id: Id,
item_count: usize,
f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
) -> UniformList<V>
where
Id: Into<ElementId>,
V: 'static,
C: Component<V>,
{
let id = id.into();
UniformList {
id: id.clone(),
style: Default::default(),
item_count,
render_items: Box::new(move |view, visible_range, cx| {
f(view, visible_range, cx)
.into_iter()
.map(|component| component.render())
.collect()
}),
interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()),
scroll_handle: None,
}
}
pub struct UniformList<V: 'static> {
id: ElementId,
style: StyleRefinement,
item_count: usize,
render_items: Box<
dyn for<'a> Fn(
&'a mut V,
Range<usize>,
&'a mut ViewContext<V>,
) -> SmallVec<[AnyElement<V>; 64]>,
>,
interactivity: StatefulInteractivity<V>,
scroll_handle: Option<UniformListScrollHandle>,
}
#[derive(Clone)]
pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
#[derive(Clone, Debug)]
struct ScrollHandleState {
item_height: Pixels,
list_height: Pixels,
scroll_offset: Arc<Mutex<Point<Pixels>>>,
}
impl UniformListScrollHandle {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(None)))
}
pub fn scroll_to_item(&self, ix: usize) {
if let Some(state) = &*self.0.lock() {
let mut scroll_offset = state.scroll_offset.lock();
let item_top = state.item_height * ix;
let item_bottom = item_top + state.item_height;
let scroll_top = -scroll_offset.y;
if item_top < scroll_top {
scroll_offset.y = -item_top;
} else if item_bottom > scroll_top + state.list_height {
scroll_offset.y = -(item_bottom - state.list_height);
}
}
}
}
impl<V: 'static> Styled for UniformList<V> {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl<V: 'static> Element<V> for UniformList<V> {
type ElementState = InteractiveElementState;
fn id(&self) -> Option<crate::ElementId> {
Some(self.id.clone())
}
fn initialize(
&mut self,
_: &mut V,
element_state: Option<Self::ElementState>,
_: &mut ViewContext<V>,
) -> Self::ElementState {
element_state.unwrap_or_default()
}
fn layout(
&mut self,
_view_state: &mut V,
_element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) -> LayoutId {
cx.request_layout(&self.computed_style(), None)
}
fn paint(
&mut self,
bounds: crate::Bounds<crate::Pixels>,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) {
let style = self.computed_style();
style.paint(bounds, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top + padding.top),
bounds.lower_right()
- point(border.right + padding.right, border.bottom + padding.bottom),
);
cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
let content_size;
if self.item_count > 0 {
let item_height = self.measure_item_height(view_state, padded_bounds, cx);
if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.lock().replace(ScrollHandleState {
item_height,
list_height: padded_bounds.size.height,
scroll_offset: element_state.track_scroll_offset(),
});
}
let visible_item_count = if item_height > px(0.) {
(padded_bounds.size.height / item_height).ceil() as usize + 1
} else {
0
};
let scroll_offset = element_state
.scroll_offset()
.map_or((0.0).into(), |offset| offset.y);
let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
let visible_range = first_visible_element_ix
..cmp::min(
first_visible_element_ix + visible_item_count,
self.item_count,
);
let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
content_size = Size {
width: padded_bounds.size.width,
height: item_height * self.item_count,
};
cx.with_z_index(1, |cx| {
for (item, ix) in items.iter_mut().zip(visible_range) {
item.initialize(view_state, cx);
let layout_id = item.layout(view_state, cx);
cx.compute_layout(
layout_id,
Size {
width: AvailableSpace::Definite(bounds.size.width),
height: AvailableSpace::Definite(item_height),
},
);
let offset =
padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
}
});
} else {
content_size = Size {
width: bounds.size.width,
height: px(0.),
};
}
let overflow = point(style.overflow.x, Overflow::Scroll);
cx.with_z_index(0, |cx| {
self.interactivity
.paint(bounds, content_size, overflow, element_state, cx);
});
})
}
}
impl<V> UniformList<V> {
fn measure_item_height(
&self,
view_state: &mut V,
list_bounds: Bounds<Pixels>,
cx: &mut ViewContext<V>,
) -> Pixels {
let mut items = (self.render_items)(view_state, 0..1, cx);
debug_assert!(items.len() == 1);
let mut item_to_measure = items.pop().unwrap();
item_to_measure.initialize(view_state, cx);
let layout_id = item_to_measure.layout(view_state, cx);
cx.compute_layout(
layout_id,
Size {
width: AvailableSpace::Definite(list_bounds.size.width),
height: AvailableSpace::MinContent,
},
);
cx.layout_bounds(layout_id).size.height
}
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
self.scroll_handle = Some(handle);
self
}
}
impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interactivity.as_stateless_mut()
}
}
impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interactivity
}
}
impl<V: 'static> Component<V> for UniformList<V> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}

View file

@ -68,7 +68,8 @@ impl<T> Future for Task<T> {
} }
} }
} }
type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
impl BackgroundExecutor { impl BackgroundExecutor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self { pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher } Self { dispatcher }
@ -81,11 +82,17 @@ impl BackgroundExecutor {
R: Send + 'static, R: Send + 'static,
{ {
let dispatcher = self.dispatcher.clone(); let dispatcher = self.dispatcher.clone();
fn inner<R: Send + 'static>(
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyFuture<R>,
) -> Task<R> {
let (runnable, task) = let (runnable, task) =
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable)); async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
runnable.schedule(); runnable.schedule();
Task::Spawned(task) Task::Spawned(task)
} }
inner::<R>(dispatcher, Box::pin(future))
}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R { pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
@ -243,12 +250,18 @@ impl ForegroundExecutor {
R: 'static, R: 'static,
{ {
let dispatcher = self.dispatcher.clone(); let dispatcher = self.dispatcher.clone();
fn inner<R: 'static>(
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyLocalFuture<R>,
) -> Task<R> {
let (runnable, task) = async_task::spawn_local(future, move |runnable| { let (runnable, task) = async_task::spawn_local(future, move |runnable| {
dispatcher.dispatch_on_main_thread(runnable) dispatcher.dispatch_on_main_thread(runnable)
}); });
runnable.schedule(); runnable.schedule();
Task::Spawned(task) Task::Spawned(task)
} }
inner::<R>(dispatcher, Box::pin(future))
}
} }
pub struct Scope<'a> { pub struct Scope<'a> {

View file

@ -267,6 +267,24 @@ impl From<Size<Pixels>> for Size<GlobalPixels> {
} }
} }
impl From<Size<Pixels>> for Size<DefiniteLength> {
fn from(size: Size<Pixels>) -> Self {
Size {
width: size.width.into(),
height: size.height.into(),
}
}
}
impl From<Size<Pixels>> for Size<AbsoluteLength> {
fn from(size: Size<Pixels>) -> Self {
Size {
width: size.width.into(),
height: size.height.into(),
}
}
}
impl Size<Length> { impl Size<Length> {
pub fn full() -> Self { pub fn full() -> Self {
Self { Self {
@ -558,6 +576,15 @@ impl Edges<DefiniteLength> {
left: px(0.).into(), left: px(0.).into(),
} }
} }
pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
Edges {
top: self.top.to_pixels(parent_size.height, rem_size),
right: self.right.to_pixels(parent_size.width, rem_size),
bottom: self.bottom.to_pixels(parent_size.height, rem_size),
left: self.left.to_pixels(parent_size.width, rem_size),
}
}
} }
impl Edges<AbsoluteLength> { impl Edges<AbsoluteLength> {
@ -689,16 +716,16 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
pub struct Pixels(pub(crate) f32); pub struct Pixels(pub(crate) f32);
impl std::ops::Div for Pixels { impl std::ops::Div for Pixels {
type Output = Self; type Output = f32;
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
Self(self.0 / rhs.0) self.0 / rhs.0
} }
} }
impl std::ops::DivAssign for Pixels { impl std::ops::DivAssign for Pixels {
fn div_assign(&mut self, rhs: Self) { fn div_assign(&mut self, rhs: Self) {
self.0 /= rhs.0; *self = Self(self.0 / rhs.0);
} }
} }
@ -750,14 +777,6 @@ impl Pixels {
pub const ZERO: Pixels = Pixels(0.0); pub const ZERO: Pixels = Pixels(0.0);
pub const MAX: Pixels = Pixels(f32::MAX); pub const MAX: Pixels = Pixels(f32::MAX);
pub fn as_usize(&self) -> usize {
self.0 as usize
}
pub fn as_isize(&self) -> isize {
self.0 as isize
}
pub fn floor(&self) -> Self { pub fn floor(&self) -> Self {
Self(self.0.floor()) Self(self.0.floor())
} }

View file

@ -24,6 +24,7 @@ mod text_system;
mod util; mod util;
mod view; mod view;
mod window; mod window;
mod window_input_handler;
mod private { mod private {
/// A mechanism for restricting implementations of a trait to only those in GPUI. /// A mechanism for restricting implementations of a trait to only those in GPUI.
@ -36,6 +37,7 @@ pub use anyhow::Result;
pub use app::*; pub use app::*;
pub use assets::*; pub use assets::*;
pub use color::*; pub use color::*;
pub use ctor::ctor;
pub use element::*; pub use element::*;
pub use elements::*; pub use elements::*;
pub use executor::*; pub use executor::*;
@ -64,6 +66,7 @@ pub use text_system::*;
pub use util::arc_cow::ArcCow; pub use util::arc_cow::ArcCow;
pub use view::*; pub use view::*;
pub use window::*; pub use window::*;
pub use window_input_handler::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use std::{ use std::{

View file

@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0)); const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
pub trait StatelessInteractive<V: 'static>: Element<V> { pub trait StatelessInteractive<V: 'static>: Element<V> {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>; fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().hover_style = f(StyleRefinement::default()); self.stateless_interactivity().hover_style = f(StyleRefinement::default());
self self
} }
@ -43,7 +43,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().group_hover_style = Some(GroupStyle { self.stateless_interactivity().group_hover_style = Some(GroupStyle {
group: group_name.into(), group: group_name.into(),
style: f(StyleRefinement::default()), style: f(StyleRefinement::default()),
}); });
@ -58,7 +58,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_down_listeners .mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
@ -79,7 +79,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_up_listeners .mouse_up_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
@ -100,7 +100,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_down_listeners .mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture if phase == DispatchPhase::Capture
@ -121,7 +121,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_up_listeners .mouse_up_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture if phase == DispatchPhase::Capture
@ -141,7 +141,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_move_listeners .mouse_move_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::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) {
@ -158,7 +158,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.scroll_wheel_listeners .scroll_wheel_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::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) {
@ -174,23 +174,48 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
C: TryInto<DispatchContext>, C: TryInto<DispatchContext>,
C::Error: Debug, C::Error: Debug,
{ {
self.stateless_interaction().dispatch_context = self.stateless_interactivity().dispatch_context =
context.try_into().expect("invalid dispatch context"); context.try_into().expect("invalid dispatch context");
self self
} }
fn on_action<A: 'static>( /// Capture the given action, fires during the capture phase
fn capture_action<A: 'static>(
mut self, mut self,
listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static, listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self ) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().key_listeners.push(( self.stateless_interactivity().key_listeners.push((
TypeId::of::<A>(), TypeId::of::<A>(),
Box::new(move |view, event, _, phase, cx| { Box::new(move |view, action, _dipatch_context, phase, cx| {
let event = event.downcast_ref().unwrap(); let action = action.downcast_ref().unwrap();
listener(view, event, phase, cx); if phase == DispatchPhase::Capture {
listener(view, action, cx)
}
None
}),
));
self
}
/// Add a listener for the given action, fires during the bubble event phase
fn on_action<A: 'static>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<A>(),
Box::new(move |view, action, _dispatch_context, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(view, action, cx)
}
None None
}), }),
)); ));
@ -204,7 +229,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().key_listeners.push(( self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyDownEvent>(), TypeId::of::<KeyDownEvent>(),
Box::new(move |view, event, _, phase, cx| { Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap(); let event = event.downcast_ref().unwrap();
@ -222,7 +247,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().key_listeners.push(( self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyUpEvent>(), TypeId::of::<KeyUpEvent>(),
Box::new(move |view, event, _, phase, cx| { Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap(); let event = event.downcast_ref().unwrap();
@ -237,7 +262,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.drag_over_styles .drag_over_styles
.push((TypeId::of::<S>(), f(StyleRefinement::default()))); .push((TypeId::of::<S>(), f(StyleRefinement::default())));
self self
@ -251,7 +276,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().group_drag_over_styles.push(( self.stateless_interactivity().group_drag_over_styles.push((
TypeId::of::<S>(), TypeId::of::<S>(),
GroupStyle { GroupStyle {
group: group_name.into(), group: group_name.into(),
@ -268,7 +293,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().drop_listeners.push(( self.stateless_interactivity().drop_listeners.push((
TypeId::of::<W>(), TypeId::of::<W>(),
Box::new(move |view, dragged_view, cx| { Box::new(move |view, dragged_view, cx| {
listener(view, dragged_view.downcast().unwrap(), cx); listener(view, dragged_view.downcast().unwrap(), cx);
@ -279,13 +304,13 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
} }
pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> { pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V>; fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.stateful_interaction().active_style = f(StyleRefinement::default()); self.stateful_interactivity().active_style = f(StyleRefinement::default());
self self
} }
@ -297,7 +322,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateful_interaction().group_active_style = Some(GroupStyle { self.stateful_interactivity().group_active_style = Some(GroupStyle {
group: group_name.into(), group: group_name.into(),
style: f(StyleRefinement::default()), style: f(StyleRefinement::default()),
}); });
@ -311,7 +336,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateful_interaction() self.stateful_interactivity()
.click_listeners .click_listeners
.push(Box::new(move |view, event, cx| listener(view, event, cx))); .push(Box::new(move |view, event, cx| listener(view, event, cx)));
self self
@ -326,10 +351,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
W: 'static + Render, W: 'static + Render,
{ {
debug_assert!( debug_assert!(
self.stateful_interaction().drag_listener.is_none(), self.stateful_interactivity().drag_listener.is_none(),
"calling on_drag more than once on the same element is not supported" "calling on_drag more than once on the same element is not supported"
); );
self.stateful_interaction().drag_listener = self.stateful_interactivity().drag_listener =
Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag { Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
view: listener(view_state, cx).into(), view: listener(view_state, cx).into(),
cursor_offset, cursor_offset,
@ -342,10 +367,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
Self: Sized, Self: Sized,
{ {
debug_assert!( debug_assert!(
self.stateful_interaction().hover_listener.is_none(), self.stateful_interactivity().hover_listener.is_none(),
"calling on_hover more than once on the same element is not supported" "calling on_hover more than once on the same element is not supported"
); );
self.stateful_interaction().hover_listener = Some(Box::new(listener)); self.stateful_interactivity().hover_listener = Some(Box::new(listener));
self self
} }
@ -358,10 +383,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
W: 'static + Render, W: 'static + Render,
{ {
debug_assert!( debug_assert!(
self.stateful_interaction().tooltip_builder.is_none(), self.stateful_interactivity().tooltip_builder.is_none(),
"calling tooltip more than once on the same element is not supported" "calling tooltip more than once on the same element is not supported"
); );
self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| { self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
build_tooltip(view_state, cx).into() build_tooltip(view_state, cx).into()
})); }));
@ -369,11 +394,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
} }
} }
pub trait ElementInteraction<V: 'static>: 'static { pub trait ElementInteractivity<V: 'static>: 'static {
fn as_stateless(&self) -> &StatelessInteraction<V>; fn as_stateless(&self) -> &StatelessInteractivity<V>;
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>; fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
fn as_stateful(&self) -> Option<&StatefulInteraction<V>>; fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>>; fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
fn initialize<R>( fn initialize<R>(
&mut self, &mut self,
@ -382,6 +407,8 @@ pub trait ElementInteraction<V: 'static>: 'static {
) -> R { ) -> R {
if let Some(stateful) = self.as_stateful_mut() { if let Some(stateful) = self.as_stateful_mut() {
cx.with_element_id(stateful.id.clone(), |global_id, cx| { cx.with_element_id(stateful.id.clone(), |global_id, cx| {
// In addition to any key down/up listeners registered directly on the element,
// we also add a key listener to match actions from the keymap.
stateful.key_listeners.push(( stateful.key_listeners.push((
TypeId::of::<KeyDownEvent>(), TypeId::of::<KeyDownEvent>(),
Box::new(move |_, key_down, context, phase, cx| { Box::new(move |_, key_down, context, phase, cx| {
@ -736,11 +763,11 @@ pub trait ElementInteraction<V: 'static>: 'static {
} }
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct StatefulInteraction<V> { pub struct StatefulInteractivity<V> {
pub id: ElementId, pub id: ElementId,
#[deref] #[deref]
#[deref_mut] #[deref_mut]
stateless: StatelessInteraction<V>, stateless: StatelessInteractivity<V>,
click_listeners: SmallVec<[ClickListener<V>; 2]>, click_listeners: SmallVec<[ClickListener<V>; 2]>,
active_style: StyleRefinement, active_style: StyleRefinement,
group_active_style: Option<GroupStyle>, group_active_style: Option<GroupStyle>,
@ -749,42 +776,42 @@ pub struct StatefulInteraction<V> {
tooltip_builder: Option<TooltipBuilder<V>>, tooltip_builder: Option<TooltipBuilder<V>>,
} }
impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> { impl<V: 'static> StatefulInteractivity<V> {
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> { pub fn new(id: ElementId, stateless: StatelessInteractivity<V>) -> Self {
Some(self)
}
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
Some(self)
}
fn as_stateless(&self) -> &StatelessInteraction<V> {
&self.stateless
}
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
&mut self.stateless
}
}
impl<V> From<ElementId> for StatefulInteraction<V> {
fn from(id: ElementId) -> Self {
Self { Self {
id, id,
stateless: StatelessInteraction::default(), stateless,
click_listeners: SmallVec::new(), click_listeners: SmallVec::new(),
active_style: StyleRefinement::default(),
group_active_style: None,
drag_listener: None, drag_listener: None,
hover_listener: None, hover_listener: None,
tooltip_builder: None, tooltip_builder: None,
active_style: StyleRefinement::default(),
group_active_style: None,
} }
} }
} }
impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
Some(self)
}
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
Some(self)
}
fn as_stateless(&self) -> &StatelessInteractivity<V> {
&self.stateless
}
fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
&mut self.stateless
}
}
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static; type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
pub struct StatelessInteraction<V> { pub struct StatelessInteractivity<V> {
pub dispatch_context: DispatchContext, pub dispatch_context: DispatchContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>, pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
@ -798,9 +825,9 @@ pub struct StatelessInteraction<V> {
drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>, drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
} }
impl<V> StatelessInteraction<V> { impl<V> StatelessInteractivity<V> {
pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteraction<V> { pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
StatefulInteraction { StatefulInteractivity {
id: id.into(), id: id.into(),
stateless: self, stateless: self,
click_listeners: SmallVec::new(), click_listeners: SmallVec::new(),
@ -876,9 +903,15 @@ impl InteractiveElementState {
.as_ref() .as_ref()
.map(|offset| offset.lock().clone()) .map(|offset| offset.lock().clone())
} }
pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
self.scroll_offset
.get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
.clone()
}
} }
impl<V> Default for StatelessInteraction<V> { impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self { fn default() -> Self {
Self { Self {
dispatch_context: DispatchContext::default(), dispatch_context: DispatchContext::default(),
@ -896,20 +929,20 @@ impl<V> Default for StatelessInteraction<V> {
} }
} }
impl<V: 'static> ElementInteraction<V> for StatelessInteraction<V> { impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> { fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
None None
} }
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> { fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
None None
} }
fn as_stateless(&self) -> &StatelessInteraction<V> { fn as_stateless(&self) -> &StatelessInteractivity<V> {
self self
} }
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> { fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
self self
} }
} }
@ -1236,7 +1269,7 @@ pub type KeyListener<V> = Box<
mod test { mod test {
use crate::{ use crate::{
self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render, self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
StatefulInteraction, StatelessInteractive, TestAppContext, VisualContext, StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
}; };
struct TestView { struct TestView {
@ -1248,19 +1281,13 @@ mod test {
actions!(TestAction); actions!(TestAction);
impl Render for TestView { impl Render for TestView {
type Element = Div<Self, StatefulInteraction<Self>>; type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child( div().id("testview").child(
div() div()
.on_key_down(|this: &mut TestView, _, _, _| { .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
dbg!("ola!"); .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
this.saw_key_down = true
})
.on_action(|this: &mut TestView, _: &TestAction, _, _| {
dbg!("ola!");
this.saw_action = true
})
.track_focus(&self.focus_handle), .track_focus(&self.focus_handle),
) )
} }

View file

@ -87,7 +87,7 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged, MTLResourceOptions::StorageModeManaged,
); );
let paths_rasterization_pipeline_state = build_pipeline_state( let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
&device, &device,
&library, &library,
"paths_rasterization", "paths_rasterization",
@ -823,7 +823,40 @@ fn build_pipeline_state(
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One); color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha); color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One); color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
device
.new_render_pipeline_state(&descriptor)
.expect("could not create render pipeline state")
}
fn build_path_rasterization_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
label: &str,
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
.expect("error locating vertex function");
let fragment_fn = library
.get_function(fragment_fn_name, None)
.expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
device device
.new_render_pipeline_state(&descriptor) .new_render_pipeline_state(&descriptor)

View file

@ -5,10 +5,11 @@ using namespace metal;
float4 hsla_to_rgba(Hsla hsla); float4 hsla_to_rgba(Hsla hsla);
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *viewport_size); constant Size_DevicePixels *viewport_size);
float2 to_tile_position(float2 unit_vertex, AtlasTile tile, float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size); constant Size_DevicePixels *atlas_size);
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds, float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii); Corners_ScaledPixels corner_radii);
float gaussian(float x, float sigma); float gaussian(float x, float sigma);
@ -21,6 +22,14 @@ struct QuadVertexOutput {
float4 background_color [[flat]]; float4 background_color [[flat]];
float4 border_color [[flat]]; float4 border_color [[flat]];
uint quad_id [[flat]]; uint quad_id [[flat]];
float clip_distance [[clip_distance]][4];
};
struct QuadFragmentInput {
float4 position [[position]];
float4 background_color [[flat]];
float4 border_color [[flat]];
uint quad_id [[flat]];
}; };
vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]], vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
@ -33,15 +42,21 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
[[buffer(QuadInputIndex_ViewportSize)]]) { [[buffer(QuadInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
Quad quad = quads[quad_id]; Quad quad = quads[quad_id];
float4 device_position = to_device_position( float4 device_position =
unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size); to_device_position(unit_vertex, quad.bounds, viewport_size);
float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
quad.content_mask.bounds);
float4 background_color = hsla_to_rgba(quad.background); float4 background_color = hsla_to_rgba(quad.background);
float4 border_color = hsla_to_rgba(quad.border_color); float4 border_color = hsla_to_rgba(quad.border_color);
return QuadVertexOutput{device_position, background_color, border_color, return QuadVertexOutput{
quad_id}; device_position,
background_color,
border_color,
quad_id,
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
} }
fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]], fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
constant Quad *quads constant Quad *quads
[[buffer(QuadInputIndex_Quads)]]) { [[buffer(QuadInputIndex_Quads)]]) {
Quad quad = quads[input.quad_id]; Quad quad = quads[input.quad_id];
@ -117,6 +132,13 @@ struct ShadowVertexOutput {
float4 position [[position]]; float4 position [[position]];
float4 color [[flat]]; float4 color [[flat]];
uint shadow_id [[flat]]; uint shadow_id [[flat]];
float clip_distance [[clip_distance]][4];
};
struct ShadowFragmentInput {
float4 position [[position]];
float4 color [[flat]];
uint shadow_id [[flat]];
}; };
vertex ShadowVertexOutput shadow_vertex( vertex ShadowVertexOutput shadow_vertex(
@ -137,18 +159,20 @@ vertex ShadowVertexOutput shadow_vertex(
bounds.size.width += 2. * margin; bounds.size.width += 2. * margin;
bounds.size.height += 2. * margin; bounds.size.height += 2. * margin;
float4 device_position = to_device_position( float4 device_position =
unit_vertex, bounds, shadow.content_mask.bounds, viewport_size); to_device_position(unit_vertex, bounds, viewport_size);
float4 clip_distance =
distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
float4 color = hsla_to_rgba(shadow.color); float4 color = hsla_to_rgba(shadow.color);
return ShadowVertexOutput{ return ShadowVertexOutput{
device_position, device_position,
color, color,
shadow_id, shadow_id,
}; {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
} }
fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]], fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
constant Shadow *shadows constant Shadow *shadows
[[buffer(ShadowInputIndex_Shadows)]]) { [[buffer(ShadowInputIndex_Shadows)]]) {
Shadow shadow = shadows[input.shadow_id]; Shadow shadow = shadows[input.shadow_id];
@ -197,6 +221,13 @@ struct UnderlineVertexOutput {
float4 position [[position]]; float4 position [[position]];
float4 color [[flat]]; float4 color [[flat]];
uint underline_id [[flat]]; uint underline_id [[flat]];
float clip_distance [[clip_distance]][4];
};
struct UnderlineFragmentInput {
float4 position [[position]];
float4 color [[flat]];
uint underline_id [[flat]];
}; };
vertex UnderlineVertexOutput underline_vertex( vertex UnderlineVertexOutput underline_vertex(
@ -208,13 +239,18 @@ vertex UnderlineVertexOutput underline_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
Underline underline = underlines[underline_id]; Underline underline = underlines[underline_id];
float4 device_position = float4 device_position =
to_device_position(unit_vertex, underline.bounds, to_device_position(unit_vertex, underline.bounds, viewport_size);
underline.content_mask.bounds, viewport_size); float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
underline.content_mask.bounds);
float4 color = hsla_to_rgba(underline.color); float4 color = hsla_to_rgba(underline.color);
return UnderlineVertexOutput{device_position, color, underline_id}; return UnderlineVertexOutput{
device_position,
color,
underline_id,
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
} }
fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]], fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
constant Underline *underlines constant Underline *underlines
[[buffer(UnderlineInputIndex_Underlines)]]) { [[buffer(UnderlineInputIndex_Underlines)]]) {
Underline underline = underlines[input.underline_id]; Underline underline = underlines[input.underline_id];
@ -244,7 +280,13 @@ struct MonochromeSpriteVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 tile_position; float2 tile_position;
float4 color [[flat]]; float4 color [[flat]];
uint sprite_id [[flat]]; float clip_distance [[clip_distance]][4];
};
struct MonochromeSpriteFragmentInput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
}; };
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
@ -255,32 +297,31 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
[[buffer(SpriteInputIndex_ViewportSize)]], [[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size constant Size_DevicePixels *atlas_size
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) { [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id]; MonochromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time float4 device_position =
// to make sampling from the texture match the mask. to_device_position(unit_vertex, sprite.bounds, viewport_size);
float4 device_position = to_device_position(unit_vertex, sprite.bounds, float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size); sprite.content_mask.bounds);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color); float4 color = hsla_to_rgba(sprite.color);
return MonochromeSpriteVertexOutput{device_position, tile_position, color, return MonochromeSpriteVertexOutput{
sprite_id}; device_position,
tile_position,
color,
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
} }
fragment float4 monochrome_sprite_fragment( fragment float4 monochrome_sprite_fragment(
MonochromeSpriteVertexOutput input [[stage_in]], MonochromeSpriteFragmentInput input [[stage_in]],
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
MonochromeSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear, constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear); min_filter::linear);
float4 sample = float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position); atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
Corners_ScaledPixels{0., 0., 0., 0.});
float4 color = input.color; float4 color = input.color;
color.a *= sample.a * saturate(0.5 - clip_distance); color.a *= sample.a;
return color; return color;
} }
@ -288,6 +329,13 @@ struct PolychromeSpriteVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 tile_position; float2 tile_position;
uint sprite_id [[flat]]; uint sprite_id [[flat]];
float clip_distance [[clip_distance]][4];
};
struct PolychromeSpriteFragmentInput {
float4 position [[position]];
float2 tile_position;
uint sprite_id [[flat]];
}; };
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex( vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
@ -301,17 +349,20 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
PolychromeSprite sprite = sprites[sprite_id]; PolychromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time float4 device_position =
// to make sampling from the texture match the mask. to_device_position(unit_vertex, sprite.bounds, viewport_size);
float4 device_position = to_device_position(unit_vertex, sprite.bounds, float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size); sprite.content_mask.bounds);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
return PolychromeSpriteVertexOutput{device_position, tile_position, return PolychromeSpriteVertexOutput{
sprite_id}; device_position,
tile_position,
sprite_id,
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
} }
fragment float4 polychrome_sprite_fragment( fragment float4 polychrome_sprite_fragment(
PolychromeSpriteVertexOutput input [[stage_in]], PolychromeSpriteFragmentInput input [[stage_in]],
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PolychromeSprite sprite = sprites[input.sprite_id]; PolychromeSprite sprite = sprites[input.sprite_id];
@ -319,11 +370,8 @@ fragment float4 polychrome_sprite_fragment(
min_filter::linear); min_filter::linear);
float4 sample = float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position); atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float quad_distance = float distance =
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
Corners_ScaledPixels{0., 0., 0., 0.});
float distance = max(quad_distance, clip_distance);
float4 color = sample; float4 color = sample;
if (sprite.grayscale) { if (sprite.grayscale) {
@ -385,7 +433,6 @@ struct PathSpriteVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 tile_position; float2 tile_position;
float4 color [[flat]]; float4 color [[flat]];
uint sprite_id [[flat]];
}; };
vertex PathSpriteVertexOutput path_sprite_vertex( vertex PathSpriteVertexOutput path_sprite_vertex(
@ -401,19 +448,17 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
PathSprite sprite = sprites[sprite_id]; PathSprite sprite = sprites[sprite_id];
// Don't apply content mask because it was already accounted for when // Don't apply content mask because it was already accounted for when
// rasterizing the path. // rasterizing the path.
float4 device_position = to_device_position(unit_vertex, sprite.bounds, float4 device_position =
sprite.bounds, viewport_size); to_device_position(unit_vertex, sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color); float4 color = hsla_to_rgba(sprite.color);
return PathSpriteVertexOutput{device_position, tile_position, color, return PathSpriteVertexOutput{device_position, tile_position, color};
sprite_id};
} }
fragment float4 path_sprite_fragment( fragment float4 path_sprite_fragment(
PathSpriteVertexOutput input [[stage_in]], PathSpriteVertexOutput input [[stage_in]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PathSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear, constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear); min_filter::linear);
float4 sample = float4 sample =
@ -473,16 +518,10 @@ float4 hsla_to_rgba(Hsla hsla) {
} }
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds, float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds,
constant Size_DevicePixels *input_viewport_size) { constant Size_DevicePixels *input_viewport_size) {
float2 position = float2 position =
unit_vertex * float2(bounds.size.width, bounds.size.height) + unit_vertex * float2(bounds.size.width, bounds.size.height) +
float2(bounds.origin.x, bounds.origin.y); float2(bounds.origin.x, bounds.origin.y);
position.x = max(clip_bounds.origin.x, position.x);
position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
position.y = max(clip_bounds.origin.y, position.y);
position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
float2 viewport_size = float2((float)input_viewport_size->width, float2 viewport_size = float2((float)input_viewport_size->width,
(float)input_viewport_size->height); (float)input_viewport_size->height);
float2 device_position = float2 device_position =
@ -551,3 +590,14 @@ float blur_along_x(float x, float y, float sigma, float corner,
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma)); 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
return integral.y - integral.x; return integral.y - integral.x;
} }
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
Bounds_ScaledPixels clip_bounds) {
float2 position =
unit_vertex * float2(bounds.size.width, bounds.size.height) +
float2(bounds.origin.x, bounds.origin.y);
return float4(position.x - clip_bounds.origin.x,
clip_bounds.origin.x + clip_bounds.size.width - position.x,
position.y - clip_bounds.origin.y,
clip_bounds.origin.y + clip_bounds.size.height - position.y);
}

View file

@ -27,8 +27,8 @@ pub(crate) struct SceneBuilder {
polychrome_sprites: Vec<PolychromeSprite>, polychrome_sprites: Vec<PolychromeSprite>,
} }
impl SceneBuilder { impl Default for SceneBuilder {
pub fn new() -> SceneBuilder { fn default() -> Self {
SceneBuilder { SceneBuilder {
layers_by_order: BTreeMap::new(), layers_by_order: BTreeMap::new(),
splitter: BspSplitter::new(), splitter: BspSplitter::new(),
@ -40,7 +40,9 @@ impl SceneBuilder {
polychrome_sprites: Vec::new(), polychrome_sprites: Vec::new(),
} }
} }
}
impl SceneBuilder {
pub fn build(&mut self) -> Scene { pub fn build(&mut self) -> Scene {
// Map each layer id to a float between 0. and 1., with 1. closer to the viewer. // Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
let mut layer_z_values = vec![0.; self.layers_by_order.len()]; let mut layer_z_values = vec![0.; self.layers_by_order.len()];

View file

@ -281,7 +281,7 @@ impl Style {
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) { pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
cx.stack(0, |cx| { cx.with_z_index(0, |cx| {
cx.paint_shadows( cx.paint_shadows(
bounds, bounds,
self.corner_radii.to_pixels(bounds.size, rem_size), self.corner_radii.to_pixels(bounds.size, rem_size),
@ -291,7 +291,7 @@ impl Style {
let background_color = self.background.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() { if background_color.is_some() || self.is_border_visible() {
cx.stack(1, |cx| { cx.with_z_index(1, |cx| {
cx.paint_quad( cx.paint_quad(
bounds, bounds,
self.corner_radii.to_pixels(bounds.size, rem_size), self.corner_radii.to_pixels(bounds.size, rem_size),

View file

@ -1,14 +1,19 @@
use crate::{ use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
SharedString, StyleRefinement, Visibility, SharedString, Style, StyleRefinement, Visibility,
}; };
use crate::{BoxShadow, TextStyleRefinement}; use crate::{BoxShadow, TextStyleRefinement};
use refineable::Refineable;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
pub trait Styled { pub trait Styled {
fn style(&mut self) -> &mut StyleRefinement; fn style(&mut self) -> &mut StyleRefinement;
fn computed_style(&mut self) -> Style {
Style::default().refined(self.style().clone())
}
gpui2_macros::style_helpers!(); gpui2_macros::style_helpers!();
/// Sets the size of the element to the full width and height. /// Sets the size of the element to the full width and height.

View file

@ -74,7 +74,6 @@ impl Line {
glyph_origin.y += line_height; glyph_origin.y += line_height;
} }
prev_glyph_position = glyph.position; prev_glyph_position = glyph.position;
let glyph_origin = glyph_origin + baseline_offset;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None; let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end { if glyph.index >= run_end {
@ -125,14 +124,14 @@ impl Line {
if max_glyph_bounds.intersects(&content_mask.bounds) { if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji { if glyph.is_emoji {
cx.paint_emoji( cx.paint_emoji(
glyph_origin, glyph_origin + baseline_offset,
run.font_id, run.font_id,
glyph.id, glyph.id,
self.layout.layout.font_size, self.layout.layout.font_size,
)?; )?;
} else { } else {
cx.paint_glyph( cx.paint_glyph(
glyph_origin, glyph_origin + baseline_offset,
run.font_id, run.font_id,
glyph.id, glyph.id,
self.layout.layout.font_size, self.layout.layout.font_size,

View file

@ -2,13 +2,14 @@ use crate::{
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, InputHandler, IsZero, KeyListener,
KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel,
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
WindowBounds, WindowInputHandler, WindowOptions, SUBPIXEL_VARIANTS,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::HashMap; use collections::HashMap;
@ -131,7 +132,12 @@ impl FocusHandle {
if self.id == ancestor_id { if self.id == ancestor_id {
return true; return true;
} else { } else {
ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied(); ancestor = cx
.window
.current_frame
.focus_parents_by_child
.get(&ancestor_id)
.copied();
} }
} }
false false
@ -174,34 +180,39 @@ pub struct Window {
pub(crate) layout_engine: TaffyLayoutEngine, pub(crate) layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView>, pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId, pub(crate) element_id_stack: GlobalElementId,
prev_frame_element_states: HashMap<GlobalElementId, AnyBox>, pub(crate) previous_frame: Frame,
element_states: HashMap<GlobalElementId, AnyBox>, pub(crate) current_frame: Frame,
prev_frame_key_matchers: HashMap<GlobalElementId, KeyMatcher>,
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>,
element_offset_stack: Vec<Point<Pixels>>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
key_dispatch_stack: Vec<KeyDispatchStackFrame>,
freeze_key_dispatch_stack: bool,
focus_stack: Vec<FocusId>,
focus_parents_by_child: HashMap<FocusId, FocusId>,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>, pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
default_prevented: bool, default_prevented: bool,
mouse_position: Point<Pixels>, mouse_position: Point<Pixels>,
requested_cursor_style: Option<CursorStyle>, requested_cursor_style: Option<CursorStyle>,
requested_input_handler: Option<Box<dyn PlatformInputHandler>>,
scale_factor: f32, scale_factor: f32,
bounds: WindowBounds, bounds: WindowBounds,
bounds_observers: SubscriberSet<(), AnyObserver>, bounds_observers: SubscriberSet<(), AnyObserver>,
active: bool, active: bool,
activation_observers: SubscriberSet<(), AnyObserver>, activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) scene_builder: SceneBuilder,
pub(crate) dirty: bool, pub(crate) dirty: bool,
pub(crate) last_blur: Option<Option<FocusId>>, pub(crate) last_blur: Option<Option<FocusId>>,
pub(crate) focus: Option<FocusId>, pub(crate) focus: Option<FocusId>,
} }
#[derive(Default)]
pub(crate) struct Frame {
element_states: HashMap<GlobalElementId, AnyBox>,
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
key_dispatch_stack: Vec<KeyDispatchStackFrame>,
freeze_key_dispatch_stack: bool,
focus_parents_by_child: HashMap<FocusId, FocusId>,
pub(crate) scene_builder: SceneBuilder,
z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>,
element_offset_stack: Vec<Point<Pixels>>,
focus_stack: Vec<FocusId>,
}
impl Window { impl Window {
pub(crate) fn new( pub(crate) fn new(
handle: AnyWindowHandle, handle: AnyWindowHandle,
@ -253,7 +264,7 @@ impl Window {
handle handle
.update(&mut cx, |_, cx| cx.dispatch_event(event)) .update(&mut cx, |_, cx| cx.dispatch_event(event))
.log_err() .log_err()
.unwrap_or(true) .unwrap_or(false)
}) })
}); });
@ -268,29 +279,18 @@ impl Window {
layout_engine: TaffyLayoutEngine::new(), layout_engine: TaffyLayoutEngine::new(),
root_view: None, root_view: None,
element_id_stack: GlobalElementId::default(), element_id_stack: GlobalElementId::default(),
prev_frame_element_states: HashMap::default(), previous_frame: Frame::default(),
element_states: HashMap::default(), current_frame: Frame::default(),
prev_frame_key_matchers: HashMap::default(),
key_matchers: HashMap::default(),
z_index_stack: StackingOrder(SmallVec::new()),
content_mask_stack: Vec::new(),
element_offset_stack: Vec::new(),
mouse_listeners: HashMap::default(),
key_dispatch_stack: Vec::new(),
freeze_key_dispatch_stack: false,
focus_stack: Vec::new(),
focus_parents_by_child: HashMap::default(),
focus_listeners: Vec::new(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
default_prevented: true, default_prevented: true,
mouse_position, mouse_position,
requested_cursor_style: None, requested_cursor_style: None,
requested_input_handler: None,
scale_factor, scale_factor,
bounds, bounds,
bounds_observers: SubscriberSet::new(), bounds_observers: SubscriberSet::new(),
active: false, active: false,
activation_observers: SubscriberSet::new(), activation_observers: SubscriberSet::new(),
scene_builder: SceneBuilder::new(),
dirty: true, dirty: true,
last_blur: None, last_blur: None,
focus: None, focus: None,
@ -560,6 +560,12 @@ impl<'a> WindowContext<'a> {
.request_measured_layout(style, rem_size, measure) .request_measured_layout(style, rem_size, measure)
} }
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
self.window
.layout_engine
.compute_layout(layout_id, available_space)
}
/// Obtain the bounds computed for the given LayoutId relative to the window. This method should not /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
/// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
/// in order to pass your element its `Bounds` automatically. /// in order to pass your element its `Bounds` automatically.
@ -605,6 +611,10 @@ impl<'a> WindowContext<'a> {
.find(|display| display.id() == self.window.display_id) .find(|display| display.id() == self.window.display_id)
} }
pub fn show_character_palette(&self) {
self.window.platform_window.show_character_palette();
}
/// The scale factor of the display associated with the window. For example, it could /// The scale factor of the display associated with the window. For example, it could
/// return 2.0 for a "retina" display, indicating that each logical pixel should actually /// return 2.0 for a "retina" display, indicating that each logical pixel should actually
/// be rendered as two pixels on screen. /// be rendered as two pixels on screen.
@ -654,8 +664,9 @@ impl<'a> WindowContext<'a> {
&mut self, &mut self,
handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
) { ) {
let order = self.window.z_index_stack.clone(); let order = self.window.current_frame.z_index_stack.clone();
self.window self.window
.current_frame
.mouse_listeners .mouse_listeners
.entry(TypeId::of::<Event>()) .entry(TypeId::of::<Event>())
.or_default() .or_default()
@ -679,9 +690,9 @@ impl<'a> WindowContext<'a> {
/// Called during painting to invoke the given closure in a new stacking context. The given /// Called during painting to invoke the given closure in a new stacking context. The given
/// z-index is interpreted relative to the previous call to `stack`. /// z-index is interpreted relative to the previous call to `stack`.
pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { pub fn stack<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
self.window.z_index_stack.push(z_index); self.window.current_frame.z_index_stack.push(z_index);
let result = f(self); let result = f(self);
self.window.z_index_stack.pop(); self.window.current_frame.z_index_stack.pop();
result result
} }
@ -699,8 +710,8 @@ impl<'a> WindowContext<'a> {
let mut shadow_bounds = bounds; let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset; shadow_bounds.origin += shadow.offset;
shadow_bounds.dilate(shadow.spread_radius); shadow_bounds.dilate(shadow.spread_radius);
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
Shadow { Shadow {
order: 0, order: 0,
bounds: shadow_bounds.scale(scale_factor), bounds: shadow_bounds.scale(scale_factor),
@ -727,8 +738,8 @@ impl<'a> WindowContext<'a> {
let content_mask = self.content_mask(); let content_mask = self.content_mask();
let window = &mut *self.window; let window = &mut *self.window;
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
Quad { Quad {
order: 0, order: 0,
bounds: bounds.scale(scale_factor), bounds: bounds.scale(scale_factor),
@ -748,9 +759,10 @@ impl<'a> WindowContext<'a> {
path.content_mask = content_mask; path.content_mask = content_mask;
path.color = color.into(); path.color = color.into();
let window = &mut *self.window; let window = &mut *self.window;
window window.current_frame.scene_builder.insert(
.scene_builder &window.current_frame.z_index_stack,
.insert(&window.z_index_stack, path.scale(scale_factor)); path.scale(scale_factor),
);
} }
/// Paint an underline into the scene for the current frame at the current z-index. /// Paint an underline into the scene for the current frame at the current z-index.
@ -772,8 +784,8 @@ impl<'a> WindowContext<'a> {
}; };
let content_mask = self.content_mask(); let content_mask = self.content_mask();
let window = &mut *self.window; let window = &mut *self.window;
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
Underline { Underline {
order: 0, order: 0,
bounds: bounds.scale(scale_factor), bounds: bounds.scale(scale_factor),
@ -787,6 +799,7 @@ impl<'a> WindowContext<'a> {
} }
/// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index. /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
/// The y component of the origin is the baseline of the glyph.
pub fn paint_glyph( pub fn paint_glyph(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,
@ -825,8 +838,8 @@ impl<'a> WindowContext<'a> {
}; };
let content_mask = self.content_mask().scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor);
let window = &mut *self.window; let window = &mut *self.window;
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
MonochromeSprite { MonochromeSprite {
order: 0, order: 0,
bounds, bounds,
@ -840,6 +853,7 @@ impl<'a> WindowContext<'a> {
} }
/// Paint an emoji glyph into the scene for the current frame at the current z-index. /// Paint an emoji glyph into the scene for the current frame at the current z-index.
/// The y component of the origin is the baseline of the glyph.
pub fn paint_emoji( pub fn paint_emoji(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,
@ -875,8 +889,8 @@ impl<'a> WindowContext<'a> {
let content_mask = self.content_mask().scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor);
let window = &mut *self.window; let window = &mut *self.window;
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
PolychromeSprite { PolychromeSprite {
order: 0, order: 0,
bounds, bounds,
@ -917,8 +931,8 @@ impl<'a> WindowContext<'a> {
let content_mask = self.content_mask().scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor);
let window = &mut *self.window; let window = &mut *self.window;
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
MonochromeSprite { MonochromeSprite {
order: 0, order: 0,
bounds, bounds,
@ -953,8 +967,8 @@ impl<'a> WindowContext<'a> {
let corner_radii = corner_radii.scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor);
let window = &mut *self.window; let window = &mut *self.window;
window.scene_builder.insert( window.current_frame.scene_builder.insert(
&window.z_index_stack, &window.current_frame.z_index_stack,
PolychromeSprite { PolychromeSprite {
order: 0, order: 0,
bounds, bounds,
@ -999,7 +1013,7 @@ impl<'a> WindowContext<'a> {
} }
self.window.root_view = Some(root_view); self.window.root_view = Some(root_view);
let scene = self.window.scene_builder.build(); let scene = self.window.current_frame.scene_builder.build();
self.window.platform_window.draw(scene); self.window.platform_window.draw(scene);
let cursor_style = self let cursor_style = self
@ -1008,43 +1022,28 @@ impl<'a> WindowContext<'a> {
.take() .take()
.unwrap_or(CursorStyle::Arrow); .unwrap_or(CursorStyle::Arrow);
self.platform.set_cursor_style(cursor_style); self.platform.set_cursor_style(cursor_style);
if let Some(handler) = self.window.requested_input_handler.take() {
self.window.platform_window.set_input_handler(handler);
}
self.window.dirty = false; self.window.dirty = false;
} }
/// Rotate the current frame and the previous frame, then clear the current frame.
/// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) { fn start_frame(&mut self) {
self.text_system().start_frame(); self.text_system().start_frame();
let window = &mut *self.window; let window = &mut *self.window;
mem::swap(&mut window.previous_frame, &mut window.current_frame);
// Move the current frame element states to the previous frame. let frame = &mut window.current_frame;
// The new empty element states map will be populated for any element states we frame.element_states.clear();
// reference during the upcoming frame. frame.key_matchers.clear();
mem::swap( frame.mouse_listeners.values_mut().for_each(Vec::clear);
&mut window.element_states, frame.focus_listeners.clear();
&mut window.prev_frame_element_states, frame.key_dispatch_stack.clear();
); frame.focus_parents_by_child.clear();
window.element_states.clear(); frame.freeze_key_dispatch_stack = false;
// Make the current key matchers the previous, and then clear the current.
// An empty key matcher map will be created for every identified element in the
// upcoming frame.
mem::swap(
&mut window.key_matchers,
&mut window.prev_frame_key_matchers,
);
window.key_matchers.clear();
// Clear mouse event listeners, because elements add new element listeners
// when the upcoming frame is painted.
window.mouse_listeners.values_mut().for_each(Vec::clear);
// Clear focus state, because we determine what is focused when the new elements
// in the upcoming frame are initialized.
window.focus_listeners.clear();
window.key_dispatch_stack.clear();
window.focus_parents_by_child.clear();
window.freeze_key_dispatch_stack = false;
} }
/// Dispatch a mouse or keyboard event on the window. /// Dispatch a mouse or keyboard event on the window.
@ -1108,6 +1107,7 @@ impl<'a> WindowContext<'a> {
if let Some(any_mouse_event) = event.mouse_event() { if let Some(any_mouse_event) = event.mouse_event() {
if let Some(mut handlers) = self if let Some(mut handlers) = self
.window .window
.current_frame
.mouse_listeners .mouse_listeners
.remove(&any_mouse_event.type_id()) .remove(&any_mouse_event.type_id())
{ {
@ -1142,17 +1142,19 @@ impl<'a> WindowContext<'a> {
// Just in case any handlers added new handlers, which is weird, but possible. // Just in case any handlers added new handlers, which is weird, but possible.
handlers.extend( handlers.extend(
self.window self.window
.current_frame
.mouse_listeners .mouse_listeners
.get_mut(&any_mouse_event.type_id()) .get_mut(&any_mouse_event.type_id())
.into_iter() .into_iter()
.flat_map(|handlers| handlers.drain(..)), .flat_map(|handlers| handlers.drain(..)),
); );
self.window self.window
.current_frame
.mouse_listeners .mouse_listeners
.insert(any_mouse_event.type_id(), handlers); .insert(any_mouse_event.type_id(), handlers);
} }
} else if let Some(any_key_event) = event.keyboard_event() { } else if let Some(any_key_event) = event.keyboard_event() {
let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack); let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack);
let key_event_type = any_key_event.type_id(); let key_event_type = any_key_event.type_id();
let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new(); let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
@ -1212,10 +1214,10 @@ impl<'a> WindowContext<'a> {
} }
drop(context_stack); drop(context_stack);
self.window.key_dispatch_stack = key_dispatch_stack; self.window.current_frame.key_dispatch_stack = key_dispatch_stack;
} }
true !self.app.propagate_event
} }
/// Attempt to map a keystroke to an action based on the keymap. /// Attempt to map a keystroke to an action based on the keymap.
@ -1227,13 +1229,14 @@ impl<'a> WindowContext<'a> {
) -> KeyMatch { ) -> KeyMatch {
let key_match = self let key_match = self
.window .window
.current_frame
.key_matchers .key_matchers
.get_mut(element_id) .get_mut(element_id)
.unwrap() .unwrap()
.match_keystroke(keystroke, context_stack); .match_keystroke(keystroke, context_stack);
if key_match.is_some() { if key_match.is_some() {
for matcher in self.window.key_matchers.values_mut() { for matcher in self.window.current_frame.key_matchers.values_mut() {
matcher.clear_pending(); matcher.clear_pending();
} }
} }
@ -1493,11 +1496,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
window.element_id_stack.push(id.into()); window.element_id_stack.push(id.into());
let global_id = window.element_id_stack.clone(); let global_id = window.element_id_stack.clone();
if window.key_matchers.get(&global_id).is_none() { if window.current_frame.key_matchers.get(&global_id).is_none() {
window.key_matchers.insert( window.current_frame.key_matchers.insert(
global_id.clone(), global_id.clone(),
window window
.prev_frame_key_matchers .previous_frame
.key_matchers
.remove(&global_id) .remove(&global_id)
.unwrap_or_else(|| KeyMatcher::new(keymap)), .unwrap_or_else(|| KeyMatcher::new(keymap)),
); );
@ -1517,9 +1521,12 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
f: impl FnOnce(&mut Self) -> R, f: impl FnOnce(&mut Self) -> R,
) -> R { ) -> R {
let mask = mask.intersect(&self.content_mask()); let mask = mask.intersect(&self.content_mask());
self.window_mut().content_mask_stack.push(mask); self.window_mut()
.current_frame
.content_mask_stack
.push(mask);
let result = f(self); let result = f(self);
self.window_mut().content_mask_stack.pop(); self.window_mut().current_frame.content_mask_stack.pop();
result result
} }
@ -1535,15 +1542,19 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
}; };
let offset = self.element_offset() + offset; let offset = self.element_offset() + offset;
self.window_mut().element_offset_stack.push(offset); self.window_mut()
.current_frame
.element_offset_stack
.push(offset);
let result = f(self); let result = f(self);
self.window_mut().element_offset_stack.pop(); self.window_mut().current_frame.element_offset_stack.pop();
result result
} }
/// Obtain the current element offset. /// Obtain the current element offset.
fn element_offset(&self) -> Point<Pixels> { fn element_offset(&self) -> Point<Pixels> {
self.window() self.window()
.current_frame
.element_offset_stack .element_offset_stack
.last() .last()
.copied() .copied()
@ -1565,9 +1576,15 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
self.with_element_id(id, |global_id, cx| { self.with_element_id(id, |global_id, cx| {
if let Some(any) = cx if let Some(any) = cx
.window_mut() .window_mut()
.current_frame
.element_states .element_states
.remove(&global_id) .remove(&global_id)
.or_else(|| cx.window_mut().prev_frame_element_states.remove(&global_id)) .or_else(|| {
cx.window_mut()
.previous_frame
.element_states
.remove(&global_id)
})
{ {
// Using the extra inner option to avoid needing to reallocate a new box. // Using the extra inner option to avoid needing to reallocate a new box.
let mut state_box = any let mut state_box = any
@ -1578,11 +1595,15 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
.expect("element state is already on the stack"); .expect("element state is already on the stack");
let (result, state) = f(Some(state), cx); let (result, state) = f(Some(state), cx);
state_box.replace(state); state_box.replace(state);
cx.window_mut().element_states.insert(global_id, state_box); cx.window_mut()
.current_frame
.element_states
.insert(global_id, state_box);
result result
} else { } else {
let (result, state) = f(None, cx); let (result, state) = f(None, cx);
cx.window_mut() cx.window_mut()
.current_frame
.element_states .element_states
.insert(global_id, Box::new(Some(state))); .insert(global_id, Box::new(Some(state)));
result result
@ -1610,6 +1631,7 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
/// Obtain the current content mask. /// Obtain the current content mask.
fn content_mask(&self) -> ContentMask<Pixels> { fn content_mask(&self) -> ContentMask<Pixels> {
self.window() self.window()
.current_frame
.content_mask_stack .content_mask_stack
.last() .last()
.cloned() .cloned()
@ -1693,10 +1715,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
&mut self.window_cx &mut self.window_cx
} }
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R { pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
self.window.z_index_stack.push(order); self.window.current_frame.z_index_stack.push(z_index);
let result = f(self); let result = f(self);
self.window.z_index_stack.pop(); self.window.current_frame.z_index_stack.pop();
result result
} }
@ -1851,7 +1873,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static, listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
) { ) {
let handle = self.view().downgrade(); let handle = self.view().downgrade();
self.window.focus_listeners.push(Box::new(move |event, cx| { self.window
.current_frame
.focus_listeners
.push(Box::new(move |event, cx| {
handle handle
.update(cx, |view, cx| listener(view, event, cx)) .update(cx, |view, cx| listener(view, event, cx))
.log_err(); .log_err();
@ -1863,8 +1888,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>, key_listeners: impl IntoIterator<Item = (TypeId, KeyListener<V>)>,
f: impl FnOnce(&mut Self) -> R, f: impl FnOnce(&mut Self) -> R,
) -> R { ) -> R {
let old_stack_len = self.window.key_dispatch_stack.len(); let old_stack_len = self.window.current_frame.key_dispatch_stack.len();
if !self.window.freeze_key_dispatch_stack { if !self.window.current_frame.freeze_key_dispatch_stack {
for (event_type, listener) in key_listeners { for (event_type, listener) in key_listeners {
let handle = self.view().downgrade(); let handle = self.view().downgrade();
let listener = Box::new( let listener = Box::new(
@ -1880,19 +1905,22 @@ impl<'a, V: 'static> ViewContext<'a, V> {
.flatten() .flatten()
}, },
); );
self.window self.window.current_frame.key_dispatch_stack.push(
.key_dispatch_stack KeyDispatchStackFrame::Listener {
.push(KeyDispatchStackFrame::Listener {
event_type, event_type,
listener, listener,
}); },
);
} }
} }
let result = f(self); let result = f(self);
if !self.window.freeze_key_dispatch_stack { if !self.window.current_frame.freeze_key_dispatch_stack {
self.window.key_dispatch_stack.truncate(old_stack_len); self.window
.current_frame
.key_dispatch_stack
.truncate(old_stack_len);
} }
result result
@ -1907,16 +1935,17 @@ impl<'a, V: 'static> ViewContext<'a, V> {
return f(self); return f(self);
} }
if !self.window.freeze_key_dispatch_stack { if !self.window.current_frame.freeze_key_dispatch_stack {
self.window self.window
.current_frame
.key_dispatch_stack .key_dispatch_stack
.push(KeyDispatchStackFrame::Context(context)); .push(KeyDispatchStackFrame::Context(context));
} }
let result = f(self); let result = f(self);
if !self.window.freeze_key_dispatch_stack { if !self.window.previous_frame.freeze_key_dispatch_stack {
self.window.key_dispatch_stack.pop(); self.window.previous_frame.key_dispatch_stack.pop();
} }
result result
@ -1927,20 +1956,21 @@ impl<'a, V: 'static> ViewContext<'a, V> {
focus_handle: FocusHandle, focus_handle: FocusHandle,
f: impl FnOnce(&mut Self) -> R, f: impl FnOnce(&mut Self) -> R,
) -> R { ) -> R {
if let Some(parent_focus_id) = self.window.focus_stack.last().copied() { if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() {
self.window self.window
.current_frame
.focus_parents_by_child .focus_parents_by_child
.insert(focus_handle.id, parent_focus_id); .insert(focus_handle.id, parent_focus_id);
} }
self.window.focus_stack.push(focus_handle.id); self.window.current_frame.focus_stack.push(focus_handle.id);
if Some(focus_handle.id) == self.window.focus { if Some(focus_handle.id) == self.window.focus {
self.window.freeze_key_dispatch_stack = true; self.window.current_frame.freeze_key_dispatch_stack = true;
} }
let result = f(self); let result = f(self);
self.window.focus_stack.pop(); self.window.current_frame.focus_stack.pop();
result result
} }
@ -1995,6 +2025,19 @@ impl<'a, V: 'static> ViewContext<'a, V> {
} }
} }
impl<V> ViewContext<'_, V>
where
V: InputHandler + 'static,
{
pub fn handle_text_input(&mut self) {
self.window.requested_input_handler = Some(Box::new(WindowInputHandler {
cx: self.app.this.clone(),
window: self.window_handle(),
handler: self.view().downgrade(),
}));
}
}
impl<V> ViewContext<'_, V> impl<V> ViewContext<'_, V>
where where
V: EventEmitter, V: EventEmitter,

View file

@ -0,0 +1,89 @@
use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView};
use std::{ops::Range, rc::Weak};
pub struct WindowInputHandler<V>
where
V: InputHandler,
{
pub cx: Weak<AppCell>,
pub window: AnyWindowHandle,
pub handler: WeakView<V>,
}
impl<V: InputHandler + 'static> PlatformInputHandler for WindowInputHandler<V> {
fn selected_text_range(&self) -> Option<std::ops::Range<usize>> {
self.update(|view, cx| view.selected_text_range(cx))
.flatten()
}
fn marked_text_range(&self) -> Option<std::ops::Range<usize>> {
self.update(|view, cx| view.marked_text_range(cx)).flatten()
}
fn text_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<String> {
self.update(|view, cx| view.text_for_range(range_utf16, cx))
.flatten()
}
fn replace_text_in_range(
&mut self,
replacement_range: Option<std::ops::Range<usize>>,
text: &str,
) {
self.update(|view, cx| view.replace_text_in_range(replacement_range, text, cx));
}
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<std::ops::Range<usize>>,
new_text: &str,
new_selected_range: Option<std::ops::Range<usize>>,
) {
self.update(|view, cx| {
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
});
}
fn unmark_text(&mut self) {
self.update(|view, cx| view.unmark_text(cx));
}
fn bounds_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<crate::Bounds<f32>> {
self.update(|view, cx| view.bounds_for_range(range_utf16, cx))
.flatten()
}
}
impl<V: InputHandler + 'static> WindowInputHandler<V> {
fn update<T>(&self, f: impl FnOnce(&mut V, &mut ViewContext<V>) -> T) -> Option<T> {
let cx = self.cx.upgrade()?;
let mut cx = cx.borrow_mut();
cx.update_window(self.window, |_, cx| self.handler.update(cx, f).ok())
.ok()?
}
}
pub trait InputHandler: Sized {
fn text_for_range(&self, range: Range<usize>, cx: &mut ViewContext<Self>) -> Option<String>;
fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
fn replace_text_in_range(
&mut self,
range: Option<Range<usize>>,
text: &str,
cx: &mut ViewContext<Self>,
);
fn replace_and_mark_text_in_range(
&mut self,
range: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
);
fn bounds_for_range(
&self,
range_utf16: std::ops::Range<usize>,
cx: &mut ViewContext<Self>,
) -> Option<crate::Bounds<f32>>;
}

View file

@ -0,0 +1,55 @@
// Input:
//
// #[action]
// struct Foo {
// bar: String,
// }
// Output:
//
// #[gpui::register_action]
// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
// struct Foo {
// bar: String,
// }
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let attrs = input
.attrs
.into_iter()
.filter(|attr| !attr.path.is_ident("action"))
.collect::<Vec<_>>();
let attributes = quote! {
#[gpui::register_action]
#[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
#(#attrs)*
};
let visibility = input.vis;
let output = match input.data {
syn::Data::Struct(ref struct_data) => {
let fields = &struct_data.fields;
quote! {
#attributes
#visibility struct #name #fields
}
}
syn::Data::Enum(ref enum_data) => {
let variants = &enum_data.variants;
quote! {
#attributes
#visibility enum #name { #variants }
}
}
_ => panic!("Expected a struct or an enum."),
};
TokenStream::from(output)
}

View file

@ -1,14 +1,26 @@
use proc_macro::TokenStream; mod action;
mod derive_component; mod derive_component;
mod register_action;
mod style_helpers; mod style_helpers;
mod test; mod test;
use proc_macro::TokenStream;
#[proc_macro] #[proc_macro]
pub fn style_helpers(args: TokenStream) -> TokenStream { pub fn style_helpers(args: TokenStream) -> TokenStream {
style_helpers::style_helpers(args) style_helpers::style_helpers(args)
} }
#[proc_macro_attribute]
pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream {
action::action(attr, item)
}
#[proc_macro_attribute]
pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
register_action::register_action(attr, item)
}
#[proc_macro_derive(Component, attributes(component))] #[proc_macro_derive(Component, attributes(component))]
pub fn derive_component(input: TokenStream) -> TokenStream { pub fn derive_component(input: TokenStream) -> TokenStream {
derive_component::derive_component(input) derive_component::derive_component(input)

View file

@ -0,0 +1,33 @@
// Input:
//
// struct FooBar {}
// Output:
//
// struct FooBar {}
//
// #[allow(non_snake_case)]
// #[gpui2::ctor]
// fn register_foobar_builder() {
// gpui2::register_action_builder::<Foo>()
// }
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let type_name = &input.ident;
let ctor_fn_name = format_ident!("register_{}_builder", type_name.to_string().to_lowercase());
let expanded = quote! {
#input
#[allow(non_snake_case)]
#[gpui::ctor]
fn #ctor_fn_name() {
gpui::register_action::<#type_name>()
}
};
TokenStream::from(expanded)
}

View file

@ -10,3 +10,4 @@ doctest = false
[dependencies] [dependencies]
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
serde = { workspace = true }

View file

@ -1,25 +1,12 @@
// todo!(use actions! macro) use gpui::actions;
#[derive(Clone, Debug, Default, PartialEq)] actions!(
pub struct Cancel; Cancel,
Confirm,
#[derive(Clone, Debug, Default, PartialEq)] SecondaryConfirm,
pub struct Confirm; SelectPrev,
SelectNext,
#[derive(Clone, Debug, Default, PartialEq)] SelectFirst,
pub struct SecondaryConfirm; SelectLast,
ShowContextMenu
#[derive(Clone, Debug, Default, PartialEq)] );
pub struct SelectPrev;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SelectNext;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SelectFirst;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct SelectLast;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ShowContextMenu;

28
crates/picker2/Cargo.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
name = "picker2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/picker2.rs"
doctest = false
[dependencies]
editor = { package = "editor2", path = "../editor2" }
gpui = { package = "gpui2", path = "../gpui2" }
menu = { package = "menu2", path = "../menu2" }
settings = { package = "settings2", path = "../settings2" }
util = { path = "../util" }
theme = { package = "theme2", path = "../theme2" }
workspace = { package = "workspace2", path = "../workspace2" }
parking_lot.workspace = true
[dev-dependencies]
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
serde_json.workspace = true
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true

View file

@ -0,0 +1,163 @@
use editor::Editor;
use gpui::{
div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
WindowContext,
};
use std::cmp;
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
scroll_handle: UniformListScrollHandle,
editor: View<Editor>,
pending_update_matches: Option<Task<Option<()>>>,
}
pub trait PickerDelegate: Sized + 'static {
type ListItem: Component<Picker<Self>>;
fn match_count(&self) -> usize;
fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
// fn placeholder_text(&self) -> Arc<str>;
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem;
}
impl<D: PickerDelegate> Picker<D> {
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let editor = cx.build_view(|cx| Editor::single_line(cx));
cx.subscribe(&editor, Self::on_input_editor_event).detach();
Self {
delegate,
scroll_handle: UniformListScrollHandle::new(),
pending_update_matches: None,
editor,
}
}
pub fn focus(&self, cx: &mut WindowContext) {
self.editor.update(cx, |editor, cx| editor.focus(cx));
}
fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
let ix = cmp::min(index + 1, count - 1);
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
}
}
fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
let ix = index.saturating_sub(1);
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
}
}
fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
self.delegate.set_selected_index(0, cx);
self.scroll_handle.scroll_to_item(0);
}
}
fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
self.delegate.set_selected_index(count - 1, cx);
self.scroll_handle.scroll_to_item(count - 1);
}
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
self.delegate.dismissed(cx);
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
self.delegate.confirm(false, cx);
}
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
self.delegate.confirm(true, cx);
}
fn on_input_editor_event(
&mut self,
_: View<Editor>,
event: &editor::Event,
cx: &mut ViewContext<Self>,
) {
if let editor::Event::BufferEdited = event {
let query = self.editor.read(cx).text(cx);
self.update_matches(query, cx);
}
}
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
let update = self.delegate.update_matches(query, cx);
self.matches_updated(cx);
self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
update.await;
this.update(&mut cx, |this, cx| {
this.matches_updated(cx);
})
.ok()
}));
}
fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
let index = self.delegate.selected_index();
self.scroll_handle.scroll_to_item(index);
self.pending_update_matches = None;
cx.notify();
}
}
impl<D: PickerDelegate> Render for Picker<D> {
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div()
.context("picker")
.id("picker-container")
.focusable()
.size_full()
.on_action(Self::select_next)
.on_action(Self::select_prev)
.on_action(Self::select_first)
.on_action(Self::select_last)
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(Self::secondary_confirm)
.child(self.editor.clone())
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
.collect()
}
})
.track_scroll(self.scroll_handle.clone())
.size_full(),
)
}
}

View file

@ -73,9 +73,9 @@ impl KeymapFile {
"Expected first item in array to be a string." "Expected first item in array to be a string."
))); )));
}; };
cx.build_action(&name, Some(data)) gpui::build_action(&name, Some(data))
} }
Value::String(name) => cx.build_action(&name, None), Value::String(name) => gpui::build_action(&name, None),
Value::Null => Ok(no_action()), Value::Null => Ok(no_action()),
_ => { _ => {
return Some(Err(anyhow!("Expected two-element array, got {action:?}"))) return Some(Err(anyhow!("Expected two-element array, got {action:?}")))

View file

@ -13,9 +13,12 @@ anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow. # TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0" backtrace-on-stack-overflow = "0.3.0"
clap = { version = "4.4", features = ["derive", "string"] } clap = { version = "4.4", features = ["derive", "string"] }
editor = { package = "editor2", path = "../editor2" }
chrono = "0.4" chrono = "0.4"
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
itertools = "0.11.0" itertools = "0.11.0"
language = { package = "language2", path = "../language2" }
log.workspace = true log.workspace = true
rust-embed.workspace = true rust-embed.workspace = true
serde.workspace = true serde.workspace = true
@ -25,8 +28,10 @@ smallvec.workspace = true
strum = { version = "0.25.0", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] }
theme = { path = "../theme" } theme = { path = "../theme" }
theme2 = { path = "../theme2" } theme2 = { path = "../theme2" }
menu = { package = "menu2", path = "../menu2" }
ui = { package = "ui2", path = "../ui2", features = ["stories"] } ui = { package = "ui2", path = "../ui2", features = ["stories"] }
util = { path = "../util" } util = { path = "../util" }
picker = { package = "picker2", path = "../picker2" }
[dev-dependencies] [dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

View file

@ -1,6 +1,7 @@
mod colors; mod colors;
mod focus; mod focus;
mod kitchen_sink; mod kitchen_sink;
mod picker;
mod scroll; mod scroll;
mod text; mod text;
mod z_index; mod z_index;
@ -8,6 +9,7 @@ mod z_index;
pub use colors::*; pub use colors::*;
pub use focus::*; pub use focus::*;
pub use kitchen_sink::*; pub use kitchen_sink::*;
pub use picker::*;
pub use scroll::*; pub use scroll::*;
pub use text::*; pub use text::*;
pub use z_index::*; pub use z_index::*;

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
StatefulInteraction, StatelessInteractive, Styled, View, VisualContext, WindowContext, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
}; };
use theme2::ActiveTheme; use theme2::ActiveTheme;
@ -15,24 +15,22 @@ impl FocusStory {
KeyBinding::new("cmd-a", ActionB, Some("child-1")), KeyBinding::new("cmd-a", ActionB, Some("child-1")),
KeyBinding::new("cmd-c", ActionC, None), KeyBinding::new("cmd-c", ActionC, None),
]); ]);
cx.register_action_type::<ActionA>();
cx.register_action_type::<ActionB>();
cx.build_view(move |cx| Self {}) cx.build_view(move |cx| Self {})
} }
} }
impl Render for FocusStory { impl Render for FocusStory {
type Element = Div<Self, StatefulInteraction<Self>, FocusEnabled<Self>>; type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme(); let theme = cx.theme();
let color_1 = theme.styles.git.created; let color_1 = theme.status().created;
let color_2 = theme.styles.git.modified; let color_2 = theme.status().modified;
let color_3 = theme.styles.git.deleted; let color_3 = theme.status().deleted;
let color_4 = theme.styles.git.conflict; let color_4 = theme.status().conflict;
let color_5 = theme.styles.git.ignored; let color_5 = theme.status().ignored;
let color_6 = theme.styles.git.renamed; let color_6 = theme.status().renamed;
let child_1 = cx.focus_handle(); let child_1 = cx.focus_handle();
let child_2 = cx.focus_handle(); let child_2 = cx.focus_handle();
@ -40,20 +38,18 @@ impl Render for FocusStory {
.id("parent") .id("parent")
.focusable() .focusable()
.context("parent") .context("parent")
.on_action(|_, action: &ActionA, phase, cx| { .on_action(|_, action: &ActionA, cx| {
println!("Action A dispatched on parent during {:?}", phase); println!("Action A dispatched on parent during");
}) })
.on_action(|_, action: &ActionB, phase, cx| { .on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on parent during {:?}", phase); println!("Action B dispatched on parent during");
}) })
.on_focus(|_, _, _| println!("Parent focused")) .on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred")) .on_blur(|_, _, _| println!("Parent blurred"))
.on_focus_in(|_, _, _| println!("Parent focus_in")) .on_focus_in(|_, _, _| println!("Parent focus_in"))
.on_focus_out(|_, _, _| println!("Parent focus_out")) .on_focus_out(|_, _, _| println!("Parent focus_out"))
.on_key_down(|_, event, phase, _| { .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event))
println!("Key down on parent {:?} {:?}", phase, event) .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event))
})
.on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event))
.size_full() .size_full()
.bg(color_1) .bg(color_1)
.focus(|style| style.bg(color_2)) .focus(|style| style.bg(color_2))
@ -62,8 +58,8 @@ impl Render for FocusStory {
div() div()
.track_focus(&child_1) .track_focus(&child_1)
.context("child-1") .context("child-1")
.on_action(|_, action: &ActionB, phase, cx| { .on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on child 1 during {:?}", phase); println!("Action B dispatched on child 1 during");
}) })
.w_full() .w_full()
.h_6() .h_6()
@ -74,20 +70,16 @@ impl Render for FocusStory {
.on_blur(|_, _, _| println!("Child 1 blurred")) .on_blur(|_, _, _| println!("Child 1 blurred"))
.on_focus_in(|_, _, _| println!("Child 1 focus_in")) .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
.on_focus_out(|_, _, _| println!("Child 1 focus_out")) .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
.on_key_down(|_, event, phase, _| { .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event))
println!("Key down on child 1 {:?} {:?}", phase, event) .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event))
})
.on_key_up(|_, event, phase, _| {
println!("Key up on child 1 {:?} {:?}", phase, event)
})
.child("Child 1"), .child("Child 1"),
) )
.child( .child(
div() div()
.track_focus(&child_2) .track_focus(&child_2)
.context("child-2") .context("child-2")
.on_action(|_, action: &ActionC, phase, cx| { .on_action(|_, action: &ActionC, cx| {
println!("Action C dispatched on child 2 during {:?}", phase); println!("Action C dispatched on child 2 during");
}) })
.w_full() .w_full()
.h_6() .h_6()
@ -96,12 +88,8 @@ impl Render for FocusStory {
.on_blur(|_, _, _| println!("Child 2 blurred")) .on_blur(|_, _, _| println!("Child 2 blurred"))
.on_focus_in(|_, _, _| println!("Child 2 focus_in")) .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
.on_focus_out(|_, _, _| println!("Child 2 focus_out")) .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
.on_key_down(|_, event, phase, _| { .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event))
println!("Key down on child 2 {:?} {:?}", phase, event) .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event))
})
.on_key_up(|_, event, phase, _| {
println!("Key up on child 2 {:?} {:?}", phase, event)
})
.child("Child 2"), .child("Child 2"),
) )
} }

View file

@ -1,5 +1,5 @@
use crate::{story::Story, story_selector::ComponentStory}; use crate::{story::Story, story_selector::ComponentStory};
use gpui::{Div, Render, StatefulInteraction, View, VisualContext}; use gpui::{Div, Render, StatefulInteractivity, View, VisualContext};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use ui::prelude::*; use ui::prelude::*;
@ -12,7 +12,7 @@ impl KitchenSinkStory {
} }
impl Render for KitchenSinkStory { impl Render for KitchenSinkStory {
type Element = Div<Self, StatefulInteraction<Self>>; type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let component_stories = ComponentStory::iter() let component_stories = ComponentStory::iter()

View file

@ -0,0 +1,214 @@
use std::sync::Arc;
use fuzzy::StringMatchCandidate;
use gpui::{
div, Component, Div, KeyBinding, ParentElement, Render, StatelessInteractive, Styled, Task,
View, VisualContext, WindowContext,
};
use picker::{Picker, PickerDelegate};
use theme2::ActiveTheme;
pub struct PickerStory {
picker: View<Picker<Delegate>>,
}
struct Delegate {
candidates: Arc<[StringMatchCandidate]>,
matches: Vec<usize>,
selected_ix: usize,
}
impl Delegate {
fn new(strings: &[&str]) -> Self {
Self {
candidates: strings
.iter()
.copied()
.enumerate()
.map(|(id, string)| StringMatchCandidate {
id,
char_bag: string.into(),
string: string.into(),
})
.collect(),
matches: vec![],
selected_ix: 0,
}
}
}
impl PickerDelegate for Delegate {
type ListItem = Div<Picker<Self>>;
fn match_count(&self) -> usize {
self.candidates.len()
}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
let Some(candidate_ix) = self.matches.get(ix) else {
return div();
};
let candidate = self.candidates[*candidate_ix].string.clone();
div()
.text_color(colors.text)
.when(selected, |s| {
s.border_l_10().border_color(colors.terminal_ansi_yellow)
})
.hover(|style| {
style
.bg(colors.element_active)
.text_color(colors.text_accent)
})
.child(candidate)
}
fn selected_index(&self) -> usize {
self.selected_ix
}
fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
self.selected_ix = ix;
cx.notify();
}
fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
let candidate_ix = self.matches[self.selected_ix];
let candidate = self.candidates[candidate_ix].string.clone();
if secondary {
eprintln!("Secondary confirmed {}", candidate)
} else {
eprintln!("Confirmed {}", candidate)
}
}
fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
cx.quit();
}
fn update_matches(
&mut self,
query: String,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Task<()> {
let candidates = self.candidates.clone();
self.matches = cx
.background_executor()
.block(fuzzy::match_strings(
&candidates,
&query,
true,
100,
&Default::default(),
cx.background_executor().clone(),
))
.into_iter()
.map(|r| r.candidate_id)
.collect();
self.selected_ix = 0;
Task::ready(())
}
}
impl PickerStory {
pub fn new(cx: &mut WindowContext) -> View<Self> {
cx.build_view(|cx| {
cx.bind_keys([
KeyBinding::new("up", menu::SelectPrev, Some("picker")),
KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
KeyBinding::new("down", menu::SelectNext, Some("picker")),
KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
KeyBinding::new("enter", menu::Confirm, Some("picker")),
KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")),
KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
KeyBinding::new("escape", menu::Cancel, Some("picker")),
KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
]);
PickerStory {
picker: cx.build_view(|cx| {
let mut delegate = Delegate::new(&[
"Baguette (France)",
"Baklava (Turkey)",
"Beef Wellington (UK)",
"Biryani (India)",
"Borscht (Ukraine)",
"Bratwurst (Germany)",
"Bulgogi (Korea)",
"Burrito (USA)",
"Ceviche (Peru)",
"Chicken Tikka Masala (India)",
"Churrasco (Brazil)",
"Couscous (North Africa)",
"Croissant (France)",
"Dim Sum (China)",
"Empanada (Argentina)",
"Fajitas (Mexico)",
"Falafel (Middle East)",
"Feijoada (Brazil)",
"Fish and Chips (UK)",
"Fondue (Switzerland)",
"Goulash (Hungary)",
"Haggis (Scotland)",
"Kebab (Middle East)",
"Kimchi (Korea)",
"Lasagna (Italy)",
"Maple Syrup Pancakes (Canada)",
"Moussaka (Greece)",
"Pad Thai (Thailand)",
"Paella (Spain)",
"Pancakes (USA)",
"Pasta Carbonara (Italy)",
"Pavlova (Australia)",
"Peking Duck (China)",
"Pho (Vietnam)",
"Pierogi (Poland)",
"Pizza (Italy)",
"Poutine (Canada)",
"Pretzel (Germany)",
"Ramen (Japan)",
"Rendang (Indonesia)",
"Sashimi (Japan)",
"Satay (Indonesia)",
"Shepherd's Pie (Ireland)",
"Sushi (Japan)",
"Tacos (Mexico)",
"Tandoori Chicken (India)",
"Tortilla (Spain)",
"Tzatziki (Greece)",
"Wiener Schnitzel (Austria)",
]);
delegate.update_matches("".into(), cx).detach();
let picker = Picker::new(delegate, cx);
picker.focus(cx);
picker
}),
}
})
}
}
impl Render for PickerStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div()
.bg(cx.theme().styles.colors.background)
.size_full()
.child(self.picker.clone())
}
}

View file

@ -1,5 +1,5 @@
use gpui::{ use gpui::{
div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled,
View, VisualContext, WindowContext, View, VisualContext, WindowContext,
}; };
use theme2::ActiveTheme; use theme2::ActiveTheme;
@ -13,12 +13,12 @@ impl ScrollStory {
} }
impl Render for ScrollStory { impl Render for ScrollStory {
type Element = Div<Self, StatefulInteraction<Self>>; type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme(); let theme = cx.theme();
let color_1 = theme.styles.git.created; let color_1 = theme.status().created;
let color_2 = theme.styles.git.modified; let color_2 = theme.status().modified;
div() div()
.id("parent") .id("parent")

View file

@ -38,6 +38,7 @@ pub enum ComponentStory {
Palette, Palette,
Panel, Panel,
ProjectPanel, ProjectPanel,
Players,
RecentProjects, RecentProjects,
Scroll, Scroll,
Tab, Tab,
@ -51,6 +52,7 @@ pub enum ComponentStory {
TrafficLights, TrafficLights,
Workspace, Workspace,
ZIndex, ZIndex,
Picker,
} }
impl ComponentStory { impl ComponentStory {
@ -79,6 +81,7 @@ impl ComponentStory {
Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(), Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(),
Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(), Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(),
Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(), Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(),
Self::Players => cx.build_view(|_| theme2::PlayerStory).into(),
Self::Panel => cx.build_view(|cx| ui::PanelStory).into(), Self::Panel => cx.build_view(|cx| ui::PanelStory).into(),
Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(), Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(),
Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(), Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(),
@ -94,6 +97,7 @@ impl ComponentStory {
Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(), Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
Self::Workspace => ui::WorkspaceStory::view(cx).into(), Self::Workspace => ui::WorkspaceStory::view(cx).into(),
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(), Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
Self::Picker => PickerStory::new(cx).into(),
} }
} }
} }

View file

@ -72,6 +72,8 @@ fn main() {
ThemeSettings::override_global(theme_settings, cx); ThemeSettings::override_global(theme_settings, cx);
ui::settings::init(cx); ui::settings::init(cx);
language::init(cx);
editor::init(cx);
let window = cx.open_window( let window = cx.open_window(
WindowOptions { WindowOptions {

View file

@ -186,9 +186,9 @@ pub fn mouse_side(
} }
pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
let col = GridCol((pos.x / cur_size.cell_width).as_usize()); let col = GridCol((cur_size.cell_width / pos.x) as usize);
let col = min(col, cur_size.last_column()); let col = min(col, cur_size.last_column());
let line = (pos.y / cur_size.line_height).as_isize() as i32; let line = (cur_size.line_height / pos.y) as i32;
let line = min(line, cur_size.bottommost_line().0); let line = min(line, cur_size.bottommost_line().0);
AlacPoint::new(GridLine(line - display_offset as i32), col) AlacPoint::new(GridLine(line - display_offset as i32), col)
} }

View file

@ -1121,8 +1121,7 @@ impl Terminal {
None => return, None => return,
}; };
let scroll_lines = let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32;
(scroll_delta / self.last_content.size.line_height).as_isize() as i32;
self.events self.events
.push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
@ -1280,11 +1279,11 @@ impl Terminal {
} }
/* Calculate the appropriate scroll lines */ /* Calculate the appropriate scroll lines */
TouchPhase::Moved => { TouchPhase::Moved => {
let old_offset = (self.scroll_px / line_height).as_isize() as i32; let old_offset = (self.scroll_px / line_height) as i32;
self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier; self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
let new_offset = (self.scroll_px / line_height).as_isize() as i32; let new_offset = (self.scroll_px / line_height) as i32;
// Whenever we hit the edges, reset our stored scroll to 0 // Whenever we hit the edges, reset our stored scroll to 0
// so we can respond to changes in direction quickly // so we can respond to changes in direction quickly
@ -1396,9 +1395,9 @@ fn all_search_matches<'a, T>(
} }
fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize { fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
let col = (pos.x / size.cell_width()).round().as_usize(); let col = (pos.x / size.cell_width()).round() as usize;
let clamped_col = min(col, size.columns() - 1); let clamped_col = min(col, size.columns() - 1);
let row = (pos.y / size.line_height()).round().as_usize(); let row = (pos.y / size.line_height()).round() as usize;
let clamped_row = min(row, size.screen_lines() - 1); let clamped_row = min(row, size.screen_lines() - 1);
clamped_row * size.columns() + clamped_col clamped_row * size.columns() + clamped_col
} }

View file

@ -5,6 +5,8 @@ edition = "2021"
publish = false publish = false
[features] [features]
default = ["stories"]
stories = ["dep:itertools"]
test-support = [ test-support = [
"gpui/test-support", "gpui/test-support",
"fs/test-support", "fs/test-support",
@ -30,6 +32,7 @@ settings = { package = "settings2", path = "../settings2" }
toml.workspace = true toml.workspace = true
uuid.workspace = true uuid.workspace = true
util = { path = "../util" } util = { path = "../util" }
itertools = { version = "0.11.0", optional = true }
[dev-dependencies] [dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use gpui::Hsla; use gpui::Hsla;
use refineable::Refineable; use refineable::Refineable;
use crate::SyntaxTheme; use crate::{PlayerColors, SyntaxTheme};
#[derive(Clone)] #[derive(Clone)]
pub struct SystemColors { pub struct SystemColors {
@ -13,33 +13,6 @@ pub struct SystemColors {
pub mac_os_traffic_light_green: Hsla, pub mac_os_traffic_light_green: Hsla,
} }
#[derive(Debug, Clone, Copy)]
pub struct PlayerColor {
pub cursor: Hsla,
pub background: Hsla,
pub selection: Hsla,
}
#[derive(Clone)]
pub struct PlayerColors(pub Vec<PlayerColor>);
impl PlayerColors {
pub fn local(&self) -> PlayerColor {
// todo!("use a valid color");
*self.0.first().unwrap()
}
pub fn absent(&self) -> PlayerColor {
// todo!("use a valid color");
*self.0.last().unwrap()
}
pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
let len = self.0.len() - 1;
self.0[(participant_index as usize % len) + 1]
}
}
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug)] #[refineable(debug)]
pub struct StatusColors { pub struct StatusColors {
@ -56,17 +29,6 @@ pub struct StatusColors {
pub warning: Hsla, pub warning: Hsla,
} }
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct GitStatusColors {
pub conflict: Hsla,
pub created: Hsla,
pub deleted: Hsla,
pub ignored: Hsla,
pub modified: Hsla,
pub renamed: Hsla,
}
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(debug, deserialize)] #[refineable(debug, deserialize)]
pub struct ThemeColors { pub struct ThemeColors {
@ -287,7 +249,6 @@ pub struct ThemeStyles {
#[refineable] #[refineable]
pub colors: ThemeColors, pub colors: ThemeColors,
pub status: StatusColors, pub status: StatusColors,
pub git: GitStatusColors,
pub player: PlayerColors, pub player: PlayerColors,
pub syntax: Arc<SyntaxTheme>, pub syntax: Arc<SyntaxTheme>,
} }

View file

@ -2,12 +2,104 @@ use std::num::ParseIntError;
use gpui::{hsla, Hsla, Rgba}; use gpui::{hsla, Hsla, Rgba};
use crate::{ use crate::colors::{StatusColors, SystemColors, ThemeColors};
colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, use crate::scale::{ColorScaleSet, ColorScales};
scale::{ColorScaleSet, ColorScales}, use crate::syntax::SyntaxTheme;
syntax::SyntaxTheme, use crate::{ColorScale, PlayerColor, PlayerColors};
ColorScale,
}; impl Default for PlayerColors {
fn default() -> Self {
Self(vec![
PlayerColor {
cursor: blue().dark().step_9(),
background: blue().dark().step_5(),
selection: blue().dark().step_3(),
},
PlayerColor {
cursor: orange().dark().step_9(),
background: orange().dark().step_5(),
selection: orange().dark().step_3(),
},
PlayerColor {
cursor: pink().dark().step_9(),
background: pink().dark().step_5(),
selection: pink().dark().step_3(),
},
PlayerColor {
cursor: lime().dark().step_9(),
background: lime().dark().step_5(),
selection: lime().dark().step_3(),
},
PlayerColor {
cursor: purple().dark().step_9(),
background: purple().dark().step_5(),
selection: purple().dark().step_3(),
},
PlayerColor {
cursor: amber().dark().step_9(),
background: amber().dark().step_5(),
selection: amber().dark().step_3(),
},
PlayerColor {
cursor: jade().dark().step_9(),
background: jade().dark().step_5(),
selection: jade().dark().step_3(),
},
PlayerColor {
cursor: red().dark().step_9(),
background: red().dark().step_5(),
selection: red().dark().step_3(),
},
])
}
}
impl PlayerColors {
pub fn default_light() -> Self {
Self(vec![
PlayerColor {
cursor: blue().light().step_9(),
background: blue().light().step_4(),
selection: blue().light().step_3(),
},
PlayerColor {
cursor: orange().light().step_9(),
background: orange().light().step_4(),
selection: orange().light().step_3(),
},
PlayerColor {
cursor: pink().light().step_9(),
background: pink().light().step_4(),
selection: pink().light().step_3(),
},
PlayerColor {
cursor: lime().light().step_9(),
background: lime().light().step_4(),
selection: lime().light().step_3(),
},
PlayerColor {
cursor: purple().light().step_9(),
background: purple().light().step_4(),
selection: purple().light().step_3(),
},
PlayerColor {
cursor: amber().light().step_9(),
background: amber().light().step_4(),
selection: amber().light().step_3(),
},
PlayerColor {
cursor: jade().light().step_9(),
background: jade().light().step_4(),
selection: jade().light().step_3(),
},
PlayerColor {
cursor: red().light().step_9(),
background: red().light().step_4(),
selection: red().light().step_3(),
},
])
}
}
fn neutral() -> ColorScaleSet { fn neutral() -> ColorScaleSet {
slate() slate()
@ -27,61 +119,21 @@ impl Default for SystemColors {
impl Default for StatusColors { impl Default for StatusColors {
fn default() -> Self { fn default() -> Self {
Self { Self {
conflict: red().dark().step_11(), conflict: red().dark().step_9(),
created: grass().dark().step_11(), created: grass().dark().step_9(),
deleted: red().dark().step_11(), deleted: red().dark().step_9(),
error: red().dark().step_11(), error: red().dark().step_9(),
hidden: neutral().dark().step_11(), hidden: neutral().dark().step_9(),
ignored: neutral().dark().step_11(), ignored: neutral().dark().step_9(),
info: blue().dark().step_11(), info: blue().dark().step_9(),
modified: yellow().dark().step_11(), modified: yellow().dark().step_9(),
renamed: blue().dark().step_11(), renamed: blue().dark().step_9(),
success: grass().dark().step_11(), success: grass().dark().step_9(),
warning: yellow().dark().step_11(), warning: yellow().dark().step_9(),
} }
} }
} }
impl Default for GitStatusColors {
fn default() -> Self {
Self {
conflict: orange().dark().step_11(),
created: grass().dark().step_11(),
deleted: red().dark().step_11(),
ignored: neutral().dark().step_11(),
modified: yellow().dark().step_11(),
renamed: blue().dark().step_11(),
}
}
}
impl Default for PlayerColors {
fn default() -> Self {
Self(vec![
PlayerColor {
cursor: hsla(0.0, 0.0, 0.0, 1.0),
background: hsla(0.0, 0.0, 0.0, 1.0),
selection: hsla(0.0, 0.0, 0.0, 1.0),
},
PlayerColor {
cursor: hsla(0.0, 0.0, 0.0, 1.0),
background: hsla(0.0, 0.0, 0.0, 1.0),
selection: hsla(0.0, 0.0, 0.0, 1.0),
},
PlayerColor {
cursor: hsla(0.0, 0.0, 0.0, 1.0),
background: hsla(0.0, 0.0, 0.0, 1.0),
selection: hsla(0.0, 0.0, 0.0, 1.0),
},
PlayerColor {
cursor: hsla(0.0, 0.0, 0.0, 1.0),
background: hsla(0.0, 0.0, 0.0, 1.0),
selection: hsla(0.0, 0.0, 0.0, 1.0),
},
])
}
}
impl SyntaxTheme { impl SyntaxTheme {
pub fn default_light() -> Self { pub fn default_light() -> Self {
Self { Self {

View file

@ -1,8 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
default_color_scales, Appearance, SyntaxTheme, Theme, ThemeFamily, default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
}; };
fn zed_pro_daylight() -> Theme { fn zed_pro_daylight() -> Theme {
@ -14,8 +14,7 @@ fn zed_pro_daylight() -> Theme {
system: SystemColors::default(), system: SystemColors::default(),
colors: ThemeColors::default_light(), colors: ThemeColors::default_light(),
status: StatusColors::default(), status: StatusColors::default(),
git: GitStatusColors::default(), player: PlayerColors::default_light(),
player: PlayerColors::default(),
syntax: Arc::new(SyntaxTheme::default_light()), syntax: Arc::new(SyntaxTheme::default_light()),
}, },
} }
@ -30,7 +29,6 @@ pub(crate) fn zed_pro_moonlight() -> Theme {
system: SystemColors::default(), system: SystemColors::default(),
colors: ThemeColors::default_dark(), colors: ThemeColors::default_dark(),
status: StatusColors::default(), status: StatusColors::default(),
git: GitStatusColors::default(),
player: PlayerColors::default(), player: PlayerColors::default(),
syntax: Arc::new(SyntaxTheme::default_dark()), syntax: Arc::new(SyntaxTheme::default_dark()),
}, },

View file

@ -0,0 +1,170 @@
use gpui::Hsla;
#[derive(Debug, Clone, Copy)]
pub struct PlayerColor {
pub cursor: Hsla,
pub background: Hsla,
pub selection: Hsla,
}
/// A collection of colors that are used to color players in the editor.
///
/// The first color is always the local player's color, usually a blue.
///
/// The rest of the default colors crisscross back and forth on the
/// color wheel so that the colors are as distinct as possible.
#[derive(Clone)]
pub struct PlayerColors(pub Vec<PlayerColor>);
impl PlayerColors {
pub fn local(&self) -> PlayerColor {
// todo!("use a valid color");
*self.0.first().unwrap()
}
pub fn absent(&self) -> PlayerColor {
// todo!("use a valid color");
*self.0.last().unwrap()
}
pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
let len = self.0.len() - 1;
self.0[(participant_index as usize % len) + 1]
}
}
#[cfg(feature = "stories")]
pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use super::*;
use crate::{ActiveTheme, Story};
use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
pub struct PlayerStory;
impl Render for PlayerStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
Story::container(cx).child(
div()
.flex()
.flex_col()
.gap_4()
.child(Story::title_for::<_, PlayerColors>(cx))
.child(Story::label(cx, "Player Colors"))
.child(
div()
.flex()
.flex_col()
.gap_1()
.child(
div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.cursor)
}),
),
)
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.background)
}),
))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div().w_8().h_8().rounded_md().bg(player.selection)
}),
)),
)
.child(Story::label(cx, "Avatar Rings"))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.my_1()
.rounded_full()
.border_2()
.border_color(player.cursor)
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size_6()
.bg(gpui::red()),
)
}),
))
.child(Story::label(cx, "Player Backgrounds"))
.child(div().flex().gap_1().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.my_1()
.rounded_xl()
.flex()
.items_center()
.h_8()
.py_0p5()
.px_1p5()
.bg(player.background)
.child(
div().relative().neg_mx_1().rounded_full().z_index(3)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
).child(
div().relative().neg_mx_1().rounded_full().z_index(2)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
).child(
div().relative().neg_mx_1().rounded_full().z_index(1)
.border_2()
.border_color(player.background)
.size(px(28.))
.child(
img()
.rounded_full()
.uri("https://avatars.githubusercontent.com/u/1714999?v=4")
.size(px(24.))
.bg(gpui::red()),
),
)
}),
))
.child(Story::label(cx, "Player Selections"))
.child(div().flex().flex_col().gap_px().children(
cx.theme().players().0.clone().iter_mut().map(|player| {
div()
.flex()
.child(
div()
.flex()
.flex_none()
.rounded_sm()
.px_0p5()
.text_color(cx.theme().colors().text)
.bg(player.selection)
.child("The brown fox jumped over the lazy dog."),
)
.child(div().flex_1())
}),
)),
)
}
}
}

View file

@ -6,8 +6,8 @@ use gpui::SharedString;
use refineable::Refineable; use refineable::Refineable;
use crate::{ use crate::{
zed_pro_family, Appearance, GitStatusColors, PlayerColors, StatusColors, SyntaxTheme, zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
}; };
pub struct ThemeRegistry { pub struct ThemeRegistry {
@ -50,7 +50,6 @@ impl ThemeRegistry {
system: SystemColors::default(), system: SystemColors::default(),
colors: theme_colors, colors: theme_colors,
status: StatusColors::default(), status: StatusColors::default(),
git: GitStatusColors::default(),
player: PlayerColors::default(), player: PlayerColors::default(),
syntax: match user_theme.appearance { syntax: match user_theme.appearance {
Appearance::Light => Arc::new(SyntaxTheme::default_light()), Appearance::Light => Arc::new(SyntaxTheme::default_light()),

View file

@ -0,0 +1,38 @@
use gpui::{div, Component, Div, ParentElement, Styled, ViewContext};
use crate::ActiveTheme;
pub struct Story {}
impl Story {
pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
div()
.size_full()
.flex()
.flex_col()
.pt_2()
.px_4()
.font("Zed Mono")
.bg(cx.theme().colors().background)
}
pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
div()
.text_xl()
.text_color(cx.theme().colors().text)
.child(title.to_owned())
}
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
Self::title(cx, std::any::type_name::<T>())
}
pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
div()
.mt_4()
.mb_2()
.text_xs()
.text_color(cx.theme().colors().text)
.child(label.to_owned())
}
}

View file

@ -1,6 +1,7 @@
mod colors; mod colors;
mod default_colors; mod default_colors;
mod default_theme; mod default_theme;
mod players;
mod registry; mod registry;
mod scale; mod scale;
mod settings; mod settings;
@ -14,6 +15,7 @@ use ::settings::Settings;
pub use colors::*; pub use colors::*;
pub use default_colors::*; pub use default_colors::*;
pub use default_theme::*; pub use default_theme::*;
pub use players::*;
pub use registry::*; pub use registry::*;
pub use scale::*; pub use scale::*;
pub use settings::*; pub use settings::*;
@ -93,19 +95,13 @@ impl Theme {
&self.styles.status &self.styles.status
} }
/// Returns the [`GitStatusColors`] for the theme.
#[inline(always)]
pub fn git(&self) -> &GitStatusColors {
&self.styles.git
}
/// Returns the color for the syntax node with the given name. /// Returns the color for the syntax node with the given name.
#[inline(always)] #[inline(always)]
pub fn syntax_color(&self, name: &str) -> Hsla { pub fn syntax_color(&self, name: &str) -> Hsla {
self.syntax().color(name) self.syntax().color(name)
} }
/// Returns the [`StatusColors`] for the theme. /// Returns the [`DiagnosticStyle`] for the theme.
#[inline(always)] #[inline(always)]
pub fn diagnostic_style(&self) -> DiagnosticStyle { pub fn diagnostic_style(&self) -> DiagnosticStyle {
DiagnosticStyle { DiagnosticStyle {
@ -126,3 +122,8 @@ pub struct DiagnosticStyle {
pub hint: Hsla, pub hint: Hsla,
pub ignored: Hsla, pub ignored: Hsla,
} }
#[cfg(feature = "stories")]
mod story;
#[cfg(feature = "stories")]
pub use story::*;

View file

@ -2,8 +2,8 @@ use std::fmt::{self, Debug};
use gpui::{Hsla, Rgba}; use gpui::{Hsla, Rgba};
use theme::{ use theme::{
Appearance, GitStatusColors, PlayerColor, PlayerColors, StatusColors, SyntaxTheme, Appearance, PlayerColor, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
SystemColors, ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement, ThemeColorsRefinement, UserTheme, UserThemeFamily, UserThemeStylesRefinement,
}; };
struct RawSyntaxPrinter<'a>(&'a str); struct RawSyntaxPrinter<'a>(&'a str);
@ -270,21 +270,6 @@ impl<'a> Debug for StatusColorsPrinter<'a> {
} }
} }
pub struct GitStatusColorsPrinter<'a>(&'a GitStatusColors);
impl<'a> Debug for GitStatusColorsPrinter<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GitStatusColors")
.field("conflict", &HslaPrinter(self.0.conflict))
.field("created", &HslaPrinter(self.0.created))
.field("deleted", &HslaPrinter(self.0.deleted))
.field("ignored", &HslaPrinter(self.0.ignored))
.field("modified", &HslaPrinter(self.0.modified))
.field("renamed", &HslaPrinter(self.0.renamed))
.finish()
}
}
pub struct PlayerColorsPrinter<'a>(&'a PlayerColors); pub struct PlayerColorsPrinter<'a>(&'a PlayerColors);
impl<'a> Debug for PlayerColorsPrinter<'a> { impl<'a> Debug for PlayerColorsPrinter<'a> {

View file

@ -128,7 +128,7 @@ impl<V: 'static> Checkbox<V> {
// click area for the checkbox. // click area for the checkbox.
.size_5() .size_5()
// Because we've enlarged the click area, we need to create a // Because we've enlarged the click area, we need to create a
// `group` to pass down interaction events to the checkbox. // `group` to pass down interactivity events to the checkbox.
.group(group_id.clone()) .group(group_id.clone())
.child( .child(
div() div()
@ -148,7 +148,7 @@ impl<V: 'static> Checkbox<V> {
.bg(bg_color) .bg(bg_color)
.border() .border()
.border_color(border_color) .border_color(border_color)
// We only want the interaction states to fire when we // We only want the interactivity states to fire when we
// are in a checkbox that isn't disabled. // are in a checkbox that isn't disabled.
.when(!self.disabled, |this| { .when(!self.disabled, |this| {
// Here instead of `hover()` we use `group_hover()` // Here instead of `hover()` we use `group_hover()`

View file

@ -46,12 +46,12 @@ pub enum GitStatus {
impl GitStatus { impl GitStatus {
pub fn hsla(&self, cx: &WindowContext) -> Hsla { pub fn hsla(&self, cx: &WindowContext) -> Hsla {
match self { match self {
Self::None => cx.theme().styles.system.transparent, Self::None => cx.theme().system().transparent,
Self::Created => cx.theme().styles.git.created, Self::Created => cx.theme().status().created,
Self::Modified => cx.theme().styles.git.modified, Self::Modified => cx.theme().status().modified,
Self::Deleted => cx.theme().styles.git.deleted, Self::Deleted => cx.theme().status().deleted,
Self::Conflict => cx.theme().styles.git.conflict, Self::Conflict => cx.theme().status().conflict,
Self::Renamed => cx.theme().styles.git.renamed, Self::Renamed => cx.theme().status().renamed,
} }
} }
} }

View file

@ -1,12 +1,10 @@
use std::{any::TypeId, sync::Arc};
use gpui::{
div, px, AnyView, AppContext, Component, DispatchPhase, Div, ParentElement, Render,
StatelessInteractive, Styled, View, ViewContext,
};
use ui::v_stack;
use crate::Workspace; use crate::Workspace;
use gpui::{
div, px, AnyView, AppContext, Component, Div, ParentElement, Render, StatelessInteractive,
Styled, View, ViewContext,
};
use std::{any::TypeId, sync::Arc};
use ui::v_stack;
pub struct ModalRegistry { pub struct ModalRegistry {
registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>, registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
@ -43,14 +41,7 @@ impl ModalRegistry {
let build_view = build_view.clone(); let build_view = build_view.clone();
div.on_action( div.on_action(
move |workspace: &mut Workspace, move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
event: &A,
phase: DispatchPhase,
cx: &mut ViewContext<Workspace>| {
if phase == DispatchPhase::Capture {
return;
}
let Some(new_modal) = (build_view)(workspace, cx) else { let Some(new_modal) = (build_view)(workspace, cx) else {
return; return;
}; };

View file

@ -2694,7 +2694,7 @@ impl Workspace {
.any(|item| item.has_conflict(cx) || item.is_dirty(cx)); .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
if is_edited != self.window_edited { if is_edited != self.window_edited {
self.window_edited = is_edited; self.window_edited = is_edited;
todo!() // todo!()
// cx.set_window_edited(self.window_edited) // cx.set_window_edited(self.window_edited)
} }
} }

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor." description = "The fast, collaborative code editor."
edition = "2021" edition = "2021"
name = "zed" name = "zed"
version = "0.112.0" version = "0.113.0"
publish = false publish = false
[lib] [lib]

View file

@ -37,4 +37,3 @@
((symbol) @comment ((symbol) @comment
(#match? @comment "^#[cC][iIsS]$")) (#match? @comment "^#[cC][iIsS]$"))

View file

@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter {
&self, &self,
cx: &mut AppContext, cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> { ) -> BoxFuture<'static, serde_json::Value> {
let action_names = cx.all_action_names().collect::<Vec<_>>(); let action_names = gpui::all_action_names();
let staff_mode = cx.is_staff(); let staff_mode = cx.is_staff();
let language_names = &self.languages.language_names(); let language_names = &self.languages.language_names();
let settings_schema = cx.global::<SettingsStore>().json_schema( let settings_schema = cx.global::<SettingsStore>().json_schema(

View file

@ -37,4 +37,3 @@
((symbol) @comment ((symbol) @comment
(#match? @comment "^#[cC][iIsS]$")) (#match? @comment "^#[cC][iIsS]$"))

View file

@ -2,7 +2,7 @@
const { execFileSync } = require("child_process"); const { execFileSync } = require("child_process");
const { GITHUB_ACCESS_TOKEN } = process.env; const { GITHUB_ACCESS_TOKEN } = process.env;
const PR_REGEX = /#\d+/ // Ex: matches on #4241 const PR_REGEX = /#\d+/; // Ex: matches on #4241
const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im; const FIXES_REGEX = /(fixes|closes|completes) (.+[/#]\d+.*)$/im;
main(); main();
@ -12,7 +12,7 @@ async function main() {
const [newTag, oldTag] = execFileSync( const [newTag, oldTag] = execFileSync(
"git", "git",
["tag", "--sort", "-committerdate"], ["tag", "--sort", "-committerdate"],
{ encoding: "utf8" } { encoding: "utf8" },
) )
.split("\n") .split("\n")
.filter((t) => t.startsWith("v") && t.endsWith("-pre")); .filter((t) => t.startsWith("v") && t.endsWith("-pre"));
@ -22,13 +22,22 @@ async function main() {
let hasProtocolChanges = false; let hasProtocolChanges = false;
try { try {
execFileSync("git", ["diff", oldTag, newTag, "--exit-code", "--", "crates/rpc"]).status != 0; execFileSync("git", [
"diff",
oldTag,
newTag,
"--exit-code",
"--",
"crates/rpc",
]).status != 0;
} catch (error) { } catch (error) {
hasProtocolChanges = true; hasProtocolChanges = true;
} }
if (hasProtocolChanges) { if (hasProtocolChanges) {
console.warn("\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n"); console.warn(
"\033[31;1;4mRPC protocol changes, server should be re-deployed\033[0m\n",
);
} else { } else {
console.log("No RPC protocol changes\n"); console.log("No RPC protocol changes\n");
} }
@ -37,10 +46,14 @@ async function main() {
const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag); const pullRequestNumbers = getPullRequestNumbers(oldTag, newTag);
// Get the PRs that were cherry-picked between main and the old tag. // Get the PRs that were cherry-picked between main and the old tag.
const existingPullRequestNumbers = new Set(getPullRequestNumbers("main", oldTag)); const existingPullRequestNumbers = new Set(
getPullRequestNumbers("main", oldTag),
);
// Filter out those existing PRs from the set of new PRs. // Filter out those existing PRs from the set of new PRs.
const newPullRequestNumbers = pullRequestNumbers.filter(number => !existingPullRequestNumbers.has(number)); const newPullRequestNumbers = pullRequestNumbers.filter(
(number) => !existingPullRequestNumbers.has(number),
);
// Fetch the pull requests from the GitHub API. // Fetch the pull requests from the GitHub API.
console.log("Merged Pull requests:"); console.log("Merged Pull requests:");
@ -56,6 +69,16 @@ async function main() {
// Print the pull request title and URL. // Print the pull request title and URL.
const pullRequest = await response.json(); const pullRequest = await response.json();
const releaseNotesHeader = /^\s*(?:Release )?Notes\s*:(.+)/ims;
let releaseNotes = pullRequest.body || "";
const captures = releaseNotesHeader.exec(releaseNotes);
const notes = captures ? captures[1] : "MISSING";
const skippableNoteRegex = /^\s*-?\s*n\/?a\s*/ims;
if (skippableNoteRegex.exec(notes) != null) {
continue;
}
console.log("*", pullRequest.title); console.log("*", pullRequest.title);
console.log(" PR URL: ", webURL); console.log(" PR URL: ", webURL);
@ -66,38 +89,30 @@ async function main() {
console.log(" Issue URL: ", fixedIssueURL); console.log(" Issue URL: ", fixedIssueURL);
} }
let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1]; releaseNotes = notes.trim().split("\n");
if (releaseNotes) {
releaseNotes = releaseNotes.trim().split("\n")
console.log(" Release Notes:"); console.log(" Release Notes:");
for (const line of releaseNotes) { for (const line of releaseNotes) {
console.log(` ${line}`); console.log(` ${line}`);
} }
}
console.log() console.log();
} }
} }
function getPullRequestNumbers(oldTag, newTag) { function getPullRequestNumbers(oldTag, newTag) {
const pullRequestNumbers = execFileSync( const pullRequestNumbers = execFileSync(
"git", "git",
[ ["log", `${oldTag}..${newTag}`, "--oneline"],
"log", { encoding: "utf8" },
`${oldTag}..${newTag}`,
"--oneline"
],
{ encoding: "utf8" }
) )
.split("\n") .split("\n")
.filter(line => line.length > 0) .filter((line) => line.length > 0)
.map(line => { .map((line) => {
const match = line.match(/#(\d+)/); const match = line.match(/#(\d+)/);
return match ? match[1] : null; return match ? match[1] : null;
}) })
.filter(line => line); .filter((line) => line);
return pullRequestNumbers; return pullRequestNumbers;
} }