Merge remote-tracking branch 'origin/main' into polish-codegen

This commit is contained in:
Antonio Scandurra 2023-09-13 11:41:32 +02:00
commit 70c9b8f8fd
183 changed files with 8343 additions and 3876 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
**/target
**/cargo-target
/zed.xcworkspace
.DS_Store
/plugins/bin

87
Cargo.lock generated
View File

@ -3164,6 +3164,7 @@ dependencies = [
"sqlez",
"sum_tree",
"taffy",
"thiserror",
"time 0.3.27",
"tiny-skia",
"usvg",
@ -3172,6 +3173,36 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "gpui2"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_more",
"futures 0.3.28",
"gpui",
"gpui2_macros",
"log",
"parking_lot 0.11.2",
"refineable",
"rust-embed",
"serde",
"settings",
"simplelog",
"smallvec",
"theme",
"util",
]
[[package]]
name = "gpui2_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "gpui_macros"
version = "0.1.0"
@ -5232,33 +5263,6 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "playground"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_more",
"gpui",
"log",
"parking_lot 0.11.2",
"playground_macros",
"refineable",
"serde",
"simplelog",
"smallvec",
"taffy",
"util",
]
[[package]]
name = "playground_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "plist"
version = "1.5.0"
@ -7368,6 +7372,21 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "storybook"
version = "0.1.0"
dependencies = [
"anyhow",
"gpui2",
"log",
"rust-embed",
"serde",
"settings",
"simplelog",
"theme",
"util",
]
[[package]]
name = "stringprep"
version = "0.1.3"
@ -7567,7 +7586,7 @@ dependencies = [
[[package]]
name = "taffy"
version = "0.3.11"
source = "git+https://github.com/DioxusLabs/taffy?rev=dab541d6104d58e2e10ce90c4a1dad0b703160cd#dab541d6104d58e2e10ce90c4a1dad0b703160cd"
source = "git+https://github.com/DioxusLabs/taffy?rev=4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e#4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e"
dependencies = [
"arrayvec 0.7.4",
"grid",
@ -8407,6 +8426,15 @@ dependencies = [
"tree-sitter",
]
[[package]]
name = "tree-sitter-nu"
version = "0.0.1"
source = "git+https://github.com/nushell/tree-sitter-nu?rev=786689b0562b9799ce53e824cb45a1a2a04dc673#786689b0562b9799ce53e824cb45a1a2a04dc673"
dependencies = [
"cc",
"tree-sitter",
]
[[package]]
name = "tree-sitter-php"
version = "0.19.1"
@ -8830,12 +8858,14 @@ dependencies = [
"collections",
"command_palette",
"editor",
"futures 0.3.28",
"gpui",
"indoc",
"itertools",
"language",
"language_selector",
"log",
"lsp",
"nvim-rs",
"parking_lot 0.11.2",
"project",
@ -9867,6 +9897,7 @@ dependencies = [
"tree-sitter-lua",
"tree-sitter-markdown",
"tree-sitter-nix",
"tree-sitter-nu",
"tree-sitter-php",
"tree-sitter-python",
"tree-sitter-racket",

View File

@ -32,9 +32,9 @@ members = [
"crates/git",
"crates/go_to_line",
"crates/gpui",
"crates/gpui/playground",
"crates/gpui/playground_macros",
"crates/gpui_macros",
"crates/gpui2",
"crates/gpui2_macros",
"crates/install_cli",
"crates/journal",
"crates/language",
@ -63,6 +63,7 @@ members = [
"crates/sqlez",
"crates/sqlez_macros",
"crates/feature_flags",
"crates/storybook",
"crates/sum_tree",
"crates/terminal",
"crates/text",
@ -141,6 +142,7 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"}
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" }

View File

@ -8,7 +8,26 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
### Dependencies
* Install [Postgres.app](https://postgresapp.com) and start it.
* Install Xcode from https://apps.apple.com/us/app/xcode/id497799835?mt=12, and accept the license:
```
sudo xcodebuild -license
```
* Install homebrew, node and rustup-init (rutup, rust, cargo, etc.)
```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install node rustup-init
rustup-init # follow the installation steps
```
* Install postgres and configure the database
```
brew install postgresql@15
brew services start postgresql@15
psql -c "CREATE ROLE postgres SUPERUSER LOGIN" postgres
psql -U postgres -c "CREATE DATABASE zed"
```
* Install the `LiveKit` server and the `foreman` process supervisor:
```
@ -41,6 +60,17 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
GITHUB_TOKEN=<$token> script/bootstrap
```
* Now try running zed with collaboration disabled:
```
cargo run
```
### Common errors
* `xcrun: error: unable to find utility "metal", not a developer tool or in PATH`
* You need to install Xcode and then run: `xcode-select --switch /Applications/Xcode.app/Contents/Developer`
* (see https://github.com/gfx-rs/gfx/issues/2309)
### Testing against locally-running servers
Start the web and collab servers:

View File

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 740 B

View File

@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 7V9.5M9.5 12V9.5M12 9.5H9.5M7 9.5H9.5M9.5 9.5L11.1667 7.83333M9.5 9.5L7.83333 11.1667M9.5 9.5L11.1667 11.1667M9.5 9.5L7.83333 7.83333" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>
<path d="M2.19366 3.84943C2.19188 4.26418 2.32864 4.59864 2.60673 4.84707C2.88052 5.09166 3.25136 5.26933 3.71609 5.3824C3.71616 5.38242 3.71623 5.38243 3.7163 5.38245L4.30919 5.53134L4.30919 5.53134L4.30965 5.53145C4.50649 5.57891 4.67124 5.63133 4.80447 5.68843L4.80469 5.68852C4.93838 5.74508 5.03564 5.81206 5.10001 5.8877L5.10001 5.8877L5.10041 5.88816C5.16432 5.96142 5.19716 6.05222 5.19716 6.16389C5.19716 6.28412 5.1609 6.38933 5.0882 6.48141C5.01496 6.57418 4.91031 6.64838 4.77141 6.70259L4.77121 6.70266C4.63472 6.75659 4.47185 6.7843 4.28146 6.7843C4.08801 6.7843 3.91607 6.75496 3.76491 6.69726C3.61654 6.6382 3.49924 6.55209 3.41132 6.43942C3.3502 6.35821 3.30747 6.26204 3.28375 6.14992C3.26238 6.04888 3.1772 5.96225 3.06518 5.96225H2.26366C2.14682 5.96225 2.04842 6.05919 2.0592 6.18012C2.08842 6.50802 2.1826 6.79102 2.34331 7.02735L2.34352 7.02767C2.53217 7.30057 2.79377 7.50587 3.12633 7.64399L3.12642 7.64402C3.46009 7.78185 3.84993 7.85 4.29476 7.85C4.74293 7.85 5.12859 7.7828 5.45023 7.64651L5.45036 7.64646C5.77328 7.50857 6.02259 7.31417 6.19551 7.06217C6.37037 6.80817 6.4579 6.50901 6.45972 6.16682L6.45972 6.16616C6.4579 5.9333 6.41513 5.72482 6.33012 5.54178C6.2474 5.35987 6.13061 5.20175 5.98007 5.06773C5.83038 4.93448 5.65389 4.82273 5.4511 4.7322C5.24919 4.64206 5.02795 4.57016 4.78757 4.51632L4.29841 4.39935L4.29841 4.39934L4.29771 4.39919C4.18081 4.37301 4.07116 4.34168 3.9687 4.30523C3.86715 4.26734 3.77847 4.22375 3.70232 4.17471C3.62796 4.12508 3.57037 4.06717 3.52849 4.00124C3.49012 3.93815 3.47157 3.86312 3.47481 3.77407L3.47484 3.77407V3.77225C3.47484 3.66563 3.50527 3.57146 3.56612 3.48808C3.6287 3.40475 3.71977 3.33801 3.84235 3.28931L3.84235 3.28932L3.84289 3.28909C3.96465 3.23906 4.1165 3.21304 4.30008 3.21304C4.57006 3.21304 4.77746 3.27105 4.92754 3.38154C5.04235 3.46608 5.11838 3.57594 5.15673 3.71259C5.18352 3.80802 5.26636 3.89142 5.37611 3.89142H6.17259C6.28852 3.89142 6.38806 3.7953 6.37515 3.67382C6.34686 3.4077 6.26051 3.16831 6.1158 2.95658C5.94159 2.70169 5.6982 2.50368 5.38762 2.36201L5.36687 2.4075M2.19366 3.84943C2.19187 3.51004 2.28242 3.21139 2.46644 2.9556L2.46658 2.9554C2.65148 2.70093 2.90447 2.50326 3.22368 2.36179C3.54316 2.2202 3.90494 2.15 4.30807 2.15C4.71809 2.15 5.07841 2.22014 5.38773 2.36206L5.36687 2.4075M2.19366 3.84943C2.19366 3.84951 2.19366 3.84959 2.19366 3.84967L2.24366 3.8494L2.19366 3.84918C2.19366 3.84926 2.19366 3.84935 2.19366 3.84943ZM5.36687 2.4075C5.06537 2.26917 4.71244 2.2 4.30807 2.2C3.91079 2.2 3.55608 2.26917 3.24394 2.4075C2.93179 2.54584 2.68616 2.73827 2.50703 2.9848L3.82389 3.24285L3.82389 3.24285C3.95336 3.18964 4.11209 3.16304 4.30008 3.16304C4.57676 3.16304 4.79579 3.22245 4.95718 3.34128C5.08094 3.43239 5.1635 3.55166 5.20487 3.69908C5.2271 3.77827 5.29386 3.84142 5.37611 3.84142H6.17259C6.26198 3.84142 6.33488 3.76799 6.32543 3.6791C6.29797 3.4208 6.21433 3.18936 6.07452 2.9848C5.90603 2.73827 5.67015 2.54584 5.36687 2.4075ZM4.78958 6.74917C4.64593 6.80592 4.47655 6.8343 4.28146 6.8343C4.08283 6.8343 3.90458 6.80415 3.74674 6.74384C3.59067 6.68177 3.46563 6.59043 3.37163 6.46983L4.78958 6.74917ZM4.78958 6.74917C4.93502 6.69241 5.04764 6.61349 5.12745 6.5124M4.78958 6.74917L5.12745 6.5124M5.12745 6.5124C5.20726 6.4113 5.24716 6.29514 5.24716 6.16389M5.12745 6.5124L5.24716 6.16389M5.24716 6.16389C5.24716 6.04152 5.2108 5.93865 5.13809 5.85529L5.24716 6.16389Z" fill="#687076" stroke="#687076" stroke-width="0.1"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -231,7 +231,14 @@
}
},
{
"context": "BufferSearchBar > Editor",
"context": "BufferSearchBar && in_replace",
"bindings": {
"enter": "search::ReplaceNext",
"cmd-enter": "search::ReplaceAll"
}
},
{
"context": "BufferSearchBar && !in_replace > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
@ -533,7 +540,7 @@
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
"cmd-alt-i": "zed::DebugElements",
"ctrl-:": "editor::ToggleInlayHints",
"ctrl-:": "editor::ToggleInlayHints"
}
},
{

View File

@ -32,6 +32,8 @@
"right": "vim::Right",
"$": "vim::EndOfLine",
"^": "vim::FirstNonWhitespace",
"_": "vim::StartOfLineDownward",
"g _": "vim::EndOfLineDownward",
"shift-g": "vim::EndOfDocument",
"w": "vim::NextWordStart",
"{": "vim::StartOfParagraph",
@ -198,6 +200,18 @@
"z c": "editor::Fold",
"z o": "editor::UnfoldLines",
"z f": "editor::FoldSelectedRanges",
"shift-z shift-q": [
"pane::CloseActiveItem",
{
"saveBehavior": "dontSave"
}
],
"shift-z shift-z": [
"pane::CloseActiveItem",
{
"saveBehavior": "promptOnConflict"
}
],
// Count support
"1": [
"vim::Number",
@ -314,8 +328,9 @@
}
},
{
"context": "Editor && vim_mode == normal && (vim_operator == none || vim_operator == n) && !VimWaiting",
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
"bindings": {
".": "vim::Repeat",
"c": [
"vim::PushOperator",
"Change"
@ -326,15 +341,12 @@
"Delete"
],
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "editor::JoinLines",
"shift-j": "vim::JoinLines",
"y": [
"vim::PushOperator",
"Yank"
],
"i": [
"vim::SwitchMode",
"Insert"
],
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
"a": "vim::InsertAfter",
"shift-a": "vim::InsertEndOfLine",
@ -379,7 +391,7 @@
}
},
{
"context": "Editor && vim_operator == n",
"context": "Editor && VimCount",
"bindings": {
"0": [
"vim::Number",
@ -448,13 +460,12 @@
],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
"shift-r": "vim::SubstituteLine",
"c": "vim::Substitute",
"~": "vim::ChangeCase",
"shift-i": [
"vim::SwitchMode",
"Insert"
],
"shift-i": "vim::InsertBefore",
"shift-a": "vim::InsertAfter",
"shift-j": "vim::JoinLines",
"r": [
"vim::PushOperator",
"Replace"
@ -488,7 +499,7 @@
"around": true
}
}
],
]
}
},
{

View File

@ -2434,14 +2434,14 @@ fn render_tree_branch(
let cap_height = row_style.cap_height(font_cache);
let baseline_offset = row_style.baseline_offset(font_cache) + (size.y() - line_height) / 2.;
Canvas::new(move |scene, bounds, _, _, _| {
scene.paint_layer(None, |scene| {
Canvas::new(move |bounds, _, _, cx| {
cx.paint_layer(None, |cx| {
let start_x = bounds.min_x() + (bounds.width() / 2.) - (branch_style.width / 2.);
let end_x = bounds.max_x();
let start_y = bounds.min_y();
let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
scene.push_quad(gpui::Quad {
cx.scene().push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, start_y),
vec2f(
@ -2453,7 +2453,7 @@ fn render_tree_branch(
border: gpui::Border::default(),
corner_radii: (0.).into(),
});
scene.push_quad(gpui::Quad {
cx.scene().push_quad(gpui::Quad {
bounds: RectF::from_points(
vec2f(start_x, end_y),
vec2f(end_x, end_y + branch_style.width),

View File

@ -13,8 +13,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
WeakViewHandle,
};
use picker::PickerEvent;
use project::{Project, RepositoryEntry};
@ -771,7 +771,7 @@ impl CollabTitlebarItem {
})
.with_tooltip::<ToggleUserMenu>(
0,
"Toggle user menu".to_owned(),
"Toggle User Menu".to_owned(),
Some(Box::new(ToggleUserMenu)),
tooltip,
cx,
@ -1165,19 +1165,18 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
&mut self,
constraint: gpui::SizeConstraint,
_: &mut CollabTitlebarItem,
_: &mut LayoutContext<CollabTitlebarItem>,
_: &mut ViewContext<CollabTitlebarItem>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut CollabTitlebarItem,
_: &mut PaintContext<CollabTitlebarItem>,
cx: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState {
let mut path = PathBuilder::new();
path.reset(bounds.lower_left());
@ -1188,7 +1187,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
path.curve_to(bounds.lower_right(), bounds.upper_right());
path.line_to(bounds.lower_left());
scene.push_path(path.build(self.color, None));
cx.scene().push_path(path.build(self.color, None));
}
fn rect_for_text_range(

View File

@ -7,7 +7,7 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
AnyElement, Axis, Element, View, ViewContext,
};
pub(crate) struct FacePile<V: View> {
@ -32,7 +32,7 @@ impl<V: View> Element<V> for FacePile<V> {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
@ -53,12 +53,11 @@ impl<V: View> Element<V> for FacePile<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@ -69,9 +68,10 @@ impl<V: View> Element<V> for FacePile<V> {
let size = face.size();
origin_x -= size.x();
let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
scene.paint_layer(None, |scene| {
face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
});
cx.scene().push_layer(None);
face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
cx.scene().pop_layer();
origin_x += self.overlap;
}

View File

@ -1,11 +1,11 @@
use collections::CommandPaletteFilter;
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
AppContext, Element, MouseState, ViewContext,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use std::cmp;
use std::cmp::{self, Reverse};
use util::ResultExt;
use workspace::Workspace;
@ -33,13 +33,18 @@ pub enum Event {
action: Box<dyn Action>,
},
}
struct Command {
name: String,
action: Box<dyn Action>,
keystrokes: Vec<Keystroke>,
}
/// Hit count for each command in the palette.
/// We only account for commands triggered directly via command palette and not by e.g. keystrokes because
/// if an user already knows a keystroke for a command, they are unlikely to use a command palette to look for it.
#[derive(Default)]
struct HitCounts(HashMap<String, usize>);
fn toggle_command_palette(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
let focused_view_id = cx.focused_view_id().unwrap_or_else(|| cx.view_id());
workspace.toggle_modal(cx, |_, cx| {
@ -83,7 +88,7 @@ impl PickerDelegate for CommandPaletteDelegate {
let view_id = self.focused_view_id;
let window = cx.window();
cx.spawn(move |picker, mut cx| async move {
let actions = window
let mut actions = window
.available_actions(view_id, &cx)
.into_iter()
.flatten()
@ -112,6 +117,16 @@ impl PickerDelegate for CommandPaletteDelegate {
}
})
.collect::<Vec<_>>();
let actions = cx.read(move |cx| {
let hit_counts = cx.optional_global::<HitCounts>();
actions.sort_by_key(|action| {
(
Reverse(hit_counts.and_then(|map| map.0.get(&action.name)).cloned()),
action.name.clone(),
)
});
actions
});
let candidates = actions
.iter()
.enumerate()
@ -166,7 +181,12 @@ impl PickerDelegate for CommandPaletteDelegate {
let window = cx.window();
let focused_view_id = self.focused_view_id;
let action_ix = self.matches[self.selected_ix].candidate_id;
let action = self.actions.remove(action_ix).action;
let command = self.actions.remove(action_ix);
cx.update_default_global(|hit_counts: &mut HitCounts, _| {
*hit_counts.0.entry(command.name).or_default() += 1;
});
let action = command.action;
cx.app_context()
.spawn(move |mut cx| async move {
window
@ -319,6 +339,21 @@ mod tests {
workspace.modal::<CommandPalette>().unwrap()
});
palette
.update(cx, |palette, cx| {
// Fill up palette's command list by running an empty query;
// we only need it to subsequently assert that the palette is initially
// sorted by command's name.
palette.delegate_mut().update_matches("".to_string(), cx)
})
.await;
palette.update(cx, |palette, _| {
let is_sorted =
|actions: &[Command]| actions.windows(2).all(|pair| pair[0].name <= pair[1].name);
assert!(is_sorted(&palette.delegate().actions));
});
palette
.update(cx, |palette, cx| {
palette

View File

@ -32,7 +32,8 @@ impl DiagnosticIndicator {
this.in_progress_checks.insert(*language_server_id);
cx.notify();
}
project::Event::DiskBasedDiagnosticsFinished { language_server_id } => {
project::Event::DiskBasedDiagnosticsFinished { language_server_id }
| project::Event::LanguageServerRemoved(language_server_id) => {
this.summary = project.read(cx).diagnostic_summary(cx);
this.in_progress_checks.remove(language_server_id);
cx.notify();

View File

@ -555,67 +555,6 @@ impl DisplaySnapshot {
})
}
/// Returns an iterator of the start positions of the occurrences of `target` in the `self` after `from`
/// Stops if `condition` returns false for any of the character position pairs observed.
pub fn find_while<'a>(
&'a self,
from: DisplayPoint,
target: &str,
condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
) -> impl Iterator<Item = DisplayPoint> + 'a {
Self::find_internal(self.chars_at(from), target.chars().collect(), condition)
}
/// Returns an iterator of the end positions of the occurrences of `target` in the `self` before `from`
/// Stops if `condition` returns false for any of the character position pairs observed.
pub fn reverse_find_while<'a>(
&'a self,
from: DisplayPoint,
target: &str,
condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
) -> impl Iterator<Item = DisplayPoint> + 'a {
Self::find_internal(
self.reverse_chars_at(from),
target.chars().rev().collect(),
condition,
)
}
fn find_internal<'a>(
iterator: impl Iterator<Item = (char, DisplayPoint)> + 'a,
target: Vec<char>,
mut condition: impl FnMut(char, DisplayPoint) -> bool + 'a,
) -> impl Iterator<Item = DisplayPoint> + 'a {
// List of partial matches with the index of the last seen character in target and the starting point of the match
let mut partial_matches: Vec<(usize, DisplayPoint)> = Vec::new();
iterator
.take_while(move |(ch, point)| condition(*ch, *point))
.filter_map(move |(ch, point)| {
if Some(&ch) == target.get(0) {
partial_matches.push((0, point));
}
let mut found = None;
// Keep partial matches that have the correct next character
partial_matches.retain_mut(|(match_position, match_start)| {
if target.get(*match_position) == Some(&ch) {
*match_position += 1;
if *match_position == target.len() {
found = Some(match_start.clone());
// This match is completed. No need to keep tracking it
false
} else {
true
}
} else {
false
}
});
found
})
}
pub fn column_to_chars(&self, display_row: u32, target: u32) -> u32 {
let mut count = 0;
let mut column = 0;
@ -933,7 +872,7 @@ pub mod tests {
use smol::stream::StreamExt;
use std::{env, sync::Arc};
use theme::SyntaxTheme;
use util::test::{marked_text_offsets, marked_text_ranges, sample_text};
use util::test::{marked_text_ranges, sample_text};
use Bias::*;
#[gpui::test(iterations = 100)]
@ -1744,32 +1683,6 @@ pub mod tests {
)
}
#[test]
fn test_find_internal() {
assert("This is a ˇtest of find internal", "test");
assert("Some text ˇaˇaˇaa with repeated characters", "aa");
fn assert(marked_text: &str, target: &str) {
let (text, expected_offsets) = marked_text_offsets(marked_text);
let chars = text
.chars()
.enumerate()
.map(|(index, ch)| (ch, DisplayPoint::new(0, index as u32)));
let target = target.chars();
assert_eq!(
expected_offsets
.into_iter()
.map(|offset| offset as u32)
.collect::<Vec<_>>(),
DisplaySnapshot::find_internal(chars, target.collect(), |_, _| true)
.map(|point| point.column())
.collect::<Vec<_>>()
)
}
}
fn syntax_chunks<'a>(
rows: Range<u32>,
map: &ModelHandle<DisplayMap>,

View File

@ -572,7 +572,7 @@ pub struct Editor {
project: Option<ModelHandle<Project>>,
focused: bool,
blink_manager: ModelHandle<BlinkManager>,
show_local_selections: bool,
pub show_local_selections: bool,
mode: EditorMode,
replica_id_mapping: Option<HashMap<ReplicaId, ReplicaId>>,
show_gutter: bool,
@ -2273,10 +2273,6 @@ impl Editor {
if self.read_only {
return;
}
if !self.input_enabled {
cx.emit(Event::InputIgnored { text });
return;
}
let selections = self.selections.all_adjusted(cx);
let mut brace_inserted = false;
@ -3211,17 +3207,30 @@ impl Editor {
.count();
let snapshot = self.buffer.read(cx).snapshot(cx);
let mut range_to_replace: Option<Range<isize>> = None;
let mut ranges = Vec::new();
for selection in &selections {
if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
let start = selection.start.saturating_sub(lookbehind);
let end = selection.end + lookahead;
if selection.id == newest_selection.id {
range_to_replace = Some(
((start + common_prefix_len) as isize - selection.start as isize)
..(end as isize - selection.start as isize),
);
}
ranges.push(start + common_prefix_len..end);
} else {
common_prefix_len = 0;
ranges.clear();
ranges.extend(selections.iter().map(|s| {
if s.id == newest_selection.id {
range_to_replace = Some(
old_range.start.to_offset_utf16(&snapshot).0 as isize
- selection.start as isize
..old_range.end.to_offset_utf16(&snapshot).0 as isize
- selection.start as isize,
);
old_range.clone()
} else {
s.start..s.end
@ -3232,6 +3241,11 @@ impl Editor {
}
let text = &text[common_prefix_len..];
cx.emit(Event::InputHandled {
utf16_range_to_replace: range_to_replace,
text: text.into(),
});
self.transact(cx, |this, cx| {
if let Some(mut snippet) = snippet {
snippet.text = text.to_string();
@ -3689,6 +3703,10 @@ impl Editor {
self.report_copilot_event(Some(completion.uuid.clone()), true, cx)
}
cx.emit(Event::InputHandled {
utf16_range_to_replace: None,
text: suggestion.text.to_string().into(),
});
self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
cx.notify();
true
@ -8437,6 +8455,41 @@ impl Editor {
pub fn inlay_hint_cache(&self) -> &InlayHintCache {
&self.inlay_hint_cache
}
pub fn replay_insert_event(
&mut self,
text: &str,
relative_utf16_range: Option<Range<isize>>,
cx: &mut ViewContext<Self>,
) {
if !self.input_enabled {
cx.emit(Event::InputIgnored { text: text.into() });
return;
}
if let Some(relative_utf16_range) = relative_utf16_range {
let selections = self.selections.all::<OffsetUtf16>(cx);
self.change_selections(None, cx, |s| {
let new_ranges = selections.into_iter().map(|range| {
let start = OffsetUtf16(
range
.head()
.0
.saturating_add_signed(relative_utf16_range.start),
);
let end = OffsetUtf16(
range
.head()
.0
.saturating_add_signed(relative_utf16_range.end),
);
start..end
});
s.select_ranges(new_ranges);
});
}
self.handle_input(text, cx);
}
}
fn document_to_inlay_range(
@ -8525,6 +8578,10 @@ pub enum Event {
InputIgnored {
text: Arc<str>,
},
InputHandled {
utf16_range_to_replace: Option<Range<isize>>,
text: Arc<str>,
},
ExcerptsAdded {
buffer: ModelHandle<Buffer>,
predecessor: ExcerptId,
@ -8742,29 +8799,51 @@ impl View for Editor {
text: &str,
cx: &mut ViewContext<Self>,
) {
self.transact(cx, |this, cx| {
if this.input_enabled {
let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
Some(this.selection_replacement_ranges(range_utf16, cx))
} else {
this.marked_text_ranges(cx)
};
if !self.input_enabled {
cx.emit(Event::InputIgnored { text: text.into() });
return;
}
if let Some(new_selected_ranges) = new_selected_ranges {
this.change_selections(None, cx, |selections| {
selections.select_ranges(new_selected_ranges)
});
}
self.transact(cx, |this, cx| {
let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
Some(this.selection_replacement_ranges(range_utf16, cx))
} else {
this.marked_text_ranges(cx)
};
let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
let newest_selection_id = this.selections.newest_anchor().id;
this.selections
.all::<OffsetUtf16>(cx)
.iter()
.zip(ranges_to_replace.iter())
.find_map(|(selection, range)| {
if selection.id == newest_selection_id {
Some(
(range.start.0 as isize - selection.head().0 as isize)
..(range.end.0 as isize - selection.head().0 as isize),
)
} else {
None
}
})
});
cx.emit(Event::InputHandled {
utf16_range_to_replace: range_to_replace,
text: text.into(),
});
if let Some(new_selected_ranges) = new_selected_ranges {
this.change_selections(None, cx, |selections| {
selections.select_ranges(new_selected_ranges)
});
}
this.handle_input(text, cx);
});
if !self.input_enabled {
return;
}
if let Some(transaction) = self.ime_transaction {
self.buffer.update(cx, |buffer, cx| {
buffer.group_until_transaction(transaction, cx);
@ -8782,6 +8861,7 @@ impl View for Editor {
cx: &mut ViewContext<Self>,
) {
if !self.input_enabled {
cx.emit(Event::InputIgnored { text: text.into() });
return;
}
@ -8806,6 +8886,29 @@ impl View for Editor {
None
};
let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
let newest_selection_id = this.selections.newest_anchor().id;
this.selections
.all::<OffsetUtf16>(cx)
.iter()
.zip(ranges_to_replace.iter())
.find_map(|(selection, range)| {
if selection.id == newest_selection_id {
Some(
(range.start.0 as isize - selection.head().0 as isize)
..(range.end.0 as isize - selection.head().0 as isize),
)
} else {
None
}
})
});
cx.emit(Event::InputHandled {
utf16_range_to_replace: range_to_replace,
text: text.into(),
});
if let Some(ranges) = ranges_to_replace {
this.change_selections(None, cx, |s| s.select_ranges(ranges));
}

View File

@ -7807,7 +7807,7 @@ fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewCo
/// Handle completion request passing a marked string specifying where the completion
/// should be triggered from using '|' character, what range should be replaced, and what completions
/// should be returned using '<' and '>' to delimit the range
fn handle_completion_request<'a>(
pub fn handle_completion_request<'a>(
cx: &mut EditorLspTestContext<'a>,
marked_string: &str,
completions: Vec<&'static str>,

View File

@ -32,8 +32,8 @@ use gpui::{
json::{self, ToJson},
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, MouseRegion, Quad,
SizeConstraint, ViewContext, WindowContext,
};
use itertools::Itertools;
use json::json;
@ -131,7 +131,6 @@ impl EditorElement {
}
fn attach_mouse_handlers(
scene: &mut SceneBuilder,
position_map: &Arc<PositionMap>,
has_popovers: bool,
visible_bounds: RectF,
@ -141,124 +140,124 @@ impl EditorElement {
cx: &mut ViewContext<Editor>,
) {
enum EditorElementMouseHandlers {}
scene.push_mouse_region(
MouseRegion::new::<EditorElementMouseHandlers>(
cx.view_id(),
cx.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();
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_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;
})
.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(
editor,
event.platform_event,
position_map.as_ref(),
text_bounds,
cx,
) {
cx.propagate_event()
if !Self::mouse_dragged(
editor,
event.platform_event,
position_map.as_ref(),
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({
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()
})
.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 {}
scene.push_mouse_region(
MouseRegion::new::<GutterHandlers>(cx.view_id(), cx.view_id() + 1, gutter_bounds)
.on_hover(|hover, editor: &mut Editor, cx| {
let view_id = cx.view_id();
let region_id = cx.view_id() + 1;
cx.scene().push_mouse_region(
MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
|hover, editor: &mut Editor, cx| {
editor.gutter_hover(
&GutterHover {
hovered: hover.started,
},
cx,
);
}),
},
),
)
}
@ -528,24 +527,24 @@ impl EditorElement {
fn paint_background(
&self,
scene: &mut SceneBuilder,
gutter_bounds: RectF,
text_bounds: RectF,
layout: &LayoutState,
cx: &mut ViewContext<Editor>,
) {
let bounds = gutter_bounds.union_rect(text_bounds);
let scroll_top =
layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: gutter_bounds,
background: Some(self.style.gutter_background),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: Default::default(),
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: text_bounds,
background: Some(self.style.background),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: Default::default(),
});
@ -570,10 +569,10 @@ impl EditorElement {
bounds.width(),
layout.position_map.line_height * (end_row - start_row + 1) as f32,
);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(origin, size),
background: Some(self.style.active_line_background),
border: Border::default(),
border: Border::default().into(),
corner_radii: Default::default(),
});
}
@ -590,10 +589,10 @@ impl EditorElement {
bounds.width(),
layout.position_map.line_height * highlighted_rows.len() as f32,
);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(origin, size),
background: Some(self.style.highlighted_line_background),
border: Border::default(),
border: Border::default().into(),
corner_radii: Default::default(),
});
}
@ -617,13 +616,13 @@ impl EditorElement {
} else {
self.style.wrap_guide
};
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(
vec2f(x, text_bounds.origin_y()),
vec2f(1., text_bounds.height()),
),
background: Some(color),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: Default::default(),
});
}
@ -632,12 +631,11 @@ impl EditorElement {
fn paint_gutter(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let line_height = layout.position_map.line_height;
@ -650,7 +648,7 @@ impl EditorElement {
);
if show_gutter {
Self::paint_diff_hunks(scene, bounds, layout, cx);
Self::paint_diff_hunks(bounds, layout, cx);
}
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
@ -661,7 +659,7 @@ impl EditorElement {
ix as f32 * line_height - (scroll_top % line_height),
);
line.paint(scene, line_origin, visible_bounds, line_height, cx);
line.paint(line_origin, visible_bounds, line_height, cx);
}
}
@ -678,7 +676,7 @@ impl EditorElement {
let indicator_origin = bounds.origin() + position + centering_offset;
indicator.paint(scene, indicator_origin, visible_bounds, editor, cx);
indicator.paint(indicator_origin, visible_bounds, editor, cx);
}
}
@ -687,22 +685,11 @@ impl EditorElement {
let mut y = *row as f32 * line_height - scroll_top;
x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
y += (line_height - indicator.size().y()) / 2.;
indicator.paint(
scene,
bounds.origin() + vec2f(x, y),
visible_bounds,
editor,
cx,
);
indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx);
}
}
fn paint_diff_hunks(
scene: &mut SceneBuilder,
bounds: RectF,
layout: &mut LayoutState,
cx: &mut ViewContext<Editor>,
) {
fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut ViewContext<Editor>) {
let diff_style = &theme::current(cx).editor.diff.clone();
let line_height = layout.position_map.line_height;
@ -721,10 +708,10 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: highlight_bounds,
background: Some(diff_style.modified),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: (1. * line_height).into(),
});
@ -754,10 +741,10 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: highlight_bounds,
background: Some(diff_style.deleted),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: (1. * line_height).into(),
});
@ -776,10 +763,10 @@ impl EditorElement {
let highlight_size = vec2f(width * 2., end_y - start_y);
let highlight_bounds = RectF::new(highlight_origin, highlight_size);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: highlight_bounds,
background: Some(color),
border: Border::new(0., Color::transparent_black()),
border: Border::new(0., Color::transparent_black()).into(),
corner_radii: (diff_style.corner_radius * line_height).into(),
});
}
@ -787,12 +774,11 @@ impl EditorElement {
fn paint_text(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let style = &self.style;
let scroll_position = layout.position_map.snapshot.scroll_position();
@ -804,9 +790,9 @@ impl EditorElement {
let line_end_overshoot = 0.15 * layout.position_map.line_height;
let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
scene.push_layer(Some(bounds));
cx.scene().push_layer(Some(bounds));
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds,
style: if !editor.link_go_to_definition_state.definitions.is_empty() {
CursorStyle::PointingHand
@ -819,7 +805,6 @@ impl EditorElement {
self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
for (id, range, color) in layout.fold_ranges.iter() {
self.paint_highlighted_range(
scene,
range.clone(),
*color,
fold_corner_radius,
@ -829,6 +814,7 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
cx,
);
for bound in range_to_bounds(
@ -840,7 +826,7 @@ impl EditorElement {
line_end_overshoot,
&layout.position_map,
) {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: bound,
style: CursorStyle::PointingHand,
});
@ -851,8 +837,9 @@ impl EditorElement {
.to_point(&layout.position_map.snapshot.display_snapshot)
.row;
scene.push_mouse_region(
MouseRegion::new::<FoldMarkers>(cx.view_id(), *id as usize, bound)
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
.on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
editor.unfold_at(&UnfoldAt { buffer_row }, cx)
})
@ -864,7 +851,6 @@ impl EditorElement {
for (range, color) in &layout.highlighted_ranges {
self.paint_highlighted_range(
scene,
range.clone(),
*color,
0.,
@ -874,6 +860,7 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
cx,
);
}
@ -891,7 +878,6 @@ impl EditorElement {
for selection in selections {
self.paint_highlighted_range(
scene,
selection.range.clone(),
selection_style.selection,
corner_radius,
@ -901,6 +887,7 @@ impl EditorElement {
scroll_top,
scroll_left,
bounds,
cx,
);
if selection.is_local && !selection.range.is_empty() {
@ -980,7 +967,6 @@ impl EditorElement {
layout,
row,
scroll_top,
scene,
content_origin,
scroll_left,
visible_text_bounds,
@ -992,14 +978,14 @@ impl EditorElement {
}
}
scene.paint_layer(Some(bounds), |scene| {
for cursor in cursors {
cursor.paint(scene, content_origin, cx);
}
});
cx.scene().push_layer(Some(bounds));
for cursor in cursors {
cursor.paint(content_origin, cx);
}
cx.scene().pop_layer();
if let Some((position, context_menu)) = layout.context_menu.as_mut() {
scene.push_stacking_context(None, None);
cx.scene().push_stacking_context(None, None);
let cursor_row_layout =
&layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
@ -1019,18 +1005,17 @@ impl EditorElement {
}
context_menu.paint(
scene,
list_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
editor,
cx,
);
scene.pop_stacking_context();
cx.scene().pop_stacking_context();
}
if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
scene.push_stacking_context(None, None);
cx.scene().push_stacking_context(None, None);
// This is safe because we check on layout whether the required row is available
let hovered_row_layout =
@ -1061,7 +1046,6 @@ impl EditorElement {
}
hover_popover.paint(
scene,
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
editor,
@ -1083,7 +1067,6 @@ impl EditorElement {
}
hover_popover.paint(
scene,
popover_origin,
RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
editor,
@ -1094,10 +1077,10 @@ impl EditorElement {
}
}
scene.pop_stacking_context();
cx.scene().pop_stacking_context();
}
scene.pop_layer();
cx.scene().pop_layer();
}
fn scrollbar_left(&self, bounds: &RectF) -> f32 {
@ -1106,11 +1089,10 @@ impl EditorElement {
fn paint_scrollbar(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
layout: &mut LayoutState,
cx: &mut ViewContext<Editor>,
editor: &Editor,
cx: &mut ViewContext<Editor>,
) {
enum ScrollbarMouseHandlers {}
if layout.mode != EditorMode::Full {
@ -1147,9 +1129,9 @@ impl EditorElement {
let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
if layout.show_scrollbars {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: track_bounds,
border: style.track.border,
border: style.track.border.into(),
background: style.track.background_color,
..Default::default()
});
@ -1177,10 +1159,10 @@ impl EditorElement {
}
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(color),
border,
border: border.into(),
corner_radii: style.thumb.corner_radii.into(),
})
};
@ -1237,29 +1219,30 @@ impl EditorElement {
left: true,
};
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(color),
border,
border: border.into(),
corner_radii: style.thumb.corner_radii.into(),
})
}
}
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: thumb_bounds,
border: style.thumb.border,
border: style.thumb.border.into(),
background: style.thumb.background_color,
corner_radii: style.thumb.corner_radii.into(),
});
}
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: track_bounds,
style: CursorStyle::Arrow,
});
scene.push_mouse_region(
MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
let region_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
.on_move(move |event, editor: &mut Editor, cx| {
if event.pressed_button.is_none() {
editor.scroll_manager.show_scrollbar(cx);
@ -1305,7 +1288,6 @@ impl EditorElement {
#[allow(clippy::too_many_arguments)]
fn paint_highlighted_range(
&self,
scene: &mut SceneBuilder,
range: Range<DisplayPoint>,
color: Color,
corner_radius: f32,
@ -1315,6 +1297,7 @@ impl EditorElement {
scroll_top: f32,
scroll_left: f32,
bounds: RectF,
cx: &mut ViewContext<Editor>,
) {
let start_row = layout.visible_display_row_range.start;
let end_row = layout.visible_display_row_range.end;
@ -1358,18 +1341,17 @@ impl EditorElement {
.collect(),
};
highlighted_range.paint(bounds, scene);
highlighted_range.paint(bounds, cx);
}
}
fn paint_blocks(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) {
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_left = scroll_position.x() * layout.position_map.em_width;
@ -1384,9 +1366,7 @@ impl EditorElement {
if !matches!(block.style, BlockStyle::Sticky) {
origin += vec2f(-scroll_left, 0.);
}
block
.element
.paint(scene, origin, visible_bounds, editor, cx);
block.element.paint(origin, visible_bounds, editor, cx);
}
}
@ -1690,7 +1670,7 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[LineWithInvisibles],
editor: &mut Editor,
cx: &mut LayoutContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> (f32, Vec<BlockLayout>) {
let mut block_id = 0;
let scroll_x = snapshot.scroll_anchor.offset.x();
@ -2022,7 +2002,6 @@ impl LineWithInvisibles {
layout: &LayoutState,
row: u32,
scroll_top: f32,
scene: &mut SceneBuilder,
content_origin: Vector2F,
scroll_left: f32,
visible_text_bounds: RectF,
@ -2035,7 +2014,6 @@ impl LineWithInvisibles {
let line_y = row as f32 * line_height - scroll_top;
self.line.paint(
scene,
content_origin + vec2f(-scroll_left, line_y),
visible_text_bounds,
line_height,
@ -2049,7 +2027,6 @@ impl LineWithInvisibles {
scroll_left,
line_y,
row,
scene,
visible_bounds,
line_height,
whitespace_setting,
@ -2065,7 +2042,6 @@ impl LineWithInvisibles {
scroll_left: f32,
line_y: f32,
row: u32,
scene: &mut SceneBuilder,
visible_bounds: RectF,
line_height: f32,
whitespace_setting: ShowWhitespaceSetting,
@ -2097,7 +2073,7 @@ impl LineWithInvisibles {
continue;
}
}
invisible_symbol.paint(scene, origin, visible_bounds, line_height, cx);
invisible_symbol.paint(origin, visible_bounds, line_height, cx);
}
}
}
@ -2116,7 +2092,7 @@ impl Element<Editor> for EditorElement {
&mut self,
constraint: SizeConstraint,
editor: &mut Editor,
cx: &mut LayoutContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
if size.x().is_infinite() {
@ -2590,15 +2566,14 @@ impl Element<Editor> for EditorElement {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
cx.scene().push_layer(Some(visible_bounds));
let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
let text_bounds = RectF::new(
@ -2607,7 +2582,6 @@ impl Element<Editor> for EditorElement {
);
Self::attach_mouse_handlers(
scene,
&layout.position_map,
layout.hover_popovers.is_some(),
visible_bounds,
@ -2617,20 +2591,19 @@ impl Element<Editor> for EditorElement {
cx,
);
self.paint_background(scene, gutter_bounds, text_bounds, layout);
self.paint_background(gutter_bounds, text_bounds, layout, cx);
if layout.gutter_size.x() > 0. {
self.paint_gutter(scene, gutter_bounds, visible_bounds, layout, editor, cx);
self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
}
self.paint_text(scene, text_bounds, visible_bounds, layout, editor, cx);
self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
scene.push_layer(Some(bounds));
cx.scene().push_layer(Some(bounds));
if !layout.blocks.is_empty() {
self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx);
self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
}
self.paint_scrollbar(scene, bounds, layout, cx, &editor);
scene.pop_layer();
scene.pop_layer();
self.paint_scrollbar(bounds, layout, &editor, cx);
cx.scene().pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(
@ -2873,7 +2846,7 @@ impl Cursor {
)
}
pub fn paint(&self, scene: &mut SceneBuilder, origin: Vector2F, cx: &mut WindowContext) {
pub fn paint(&self, origin: Vector2F, cx: &mut WindowContext) {
let bounds = match self.shape {
CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
CursorShape::Block | CursorShape::Hollow => RectF::new(
@ -2888,14 +2861,14 @@ impl Cursor {
//Draw background or border quad
if matches!(self.shape, CursorShape::Hollow) {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: None,
border: Border::all(1., self.color),
border: Border::all(1., self.color).into(),
corner_radii: Default::default(),
});
} else {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(self.color),
border: Default::default(),
@ -2904,7 +2877,7 @@ impl Cursor {
}
if let Some(block_text) = &self.block_text {
block_text.paint(scene, self.origin + origin, bounds, self.line_height, cx);
block_text.paint(self.origin + origin, bounds, self.line_height, cx);
}
}
@ -2929,17 +2902,17 @@ pub struct HighlightedRangeLine {
}
impl HighlightedRange {
pub fn paint(&self, bounds: RectF, scene: &mut SceneBuilder) {
pub fn paint(&self, bounds: RectF, cx: &mut WindowContext) {
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
self.paint_lines(
self.start_y + self.line_height,
&self.lines[1..],
bounds,
scene,
cx,
);
} else {
self.paint_lines(self.start_y, &self.lines, bounds, scene);
self.paint_lines(self.start_y, &self.lines, bounds, cx);
}
}
@ -2948,7 +2921,7 @@ impl HighlightedRange {
start_y: f32,
lines: &[HighlightedRangeLine],
bounds: RectF,
scene: &mut SceneBuilder,
cx: &mut WindowContext,
) {
if lines.is_empty() {
return;
@ -3046,7 +3019,7 @@ impl HighlightedRange {
}
path.line_to(first_top_right - top_curve_width);
scene.push_path(path.build(self.color, Some(bounds)));
cx.scene().push_path(path.build(self.color, Some(bounds)));
}
}
@ -3204,18 +3177,10 @@ mod tests {
Point::new(5, 6)..Point::new(6, 0),
]);
});
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
&mut layout_cx,
cx,
)
});
assert_eq!(state.selections.len(), 1);
@ -3296,18 +3261,10 @@ mod tests {
DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
]);
});
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
&mut layout_cx,
cx,
)
});
@ -3363,18 +3320,10 @@ mod tests {
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (size, mut state) = editor.update(cx, |editor, cx| {
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
editor,
&mut layout_cx,
cx,
)
});
@ -3389,17 +3338,9 @@ mod tests {
);
// Don't panic.
let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size);
editor.update(cx, |editor, cx| {
element.paint(
&mut scene,
bounds,
bounds,
&mut state,
editor,
&mut PaintContext::new(cx),
);
element.paint(bounds, bounds, &mut state, editor, cx);
});
}
@ -3567,18 +3508,10 @@ mod tests {
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
editor.set_wrap_width(Some(editor_width), cx);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
element.layout(
SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)),
editor,
&mut layout_cx,
cx,
)
});

View File

@ -691,15 +691,15 @@ impl InfoPopover {
.with_highlights(rendered_content.highlights.clone())
.with_custom_runs(
rendered_content.region_ranges.clone(),
move |ix, bounds, scene, _| {
move |ix, bounds, cx| {
region_id += 1;
let region = regions[ix].clone();
if let Some(url) = region.link_url {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds,
style: CursorStyle::PointingHand,
});
scene.push_mouse_region(
cx.scene().push_mouse_region(
MouseRegion::new::<Self>(view_id, region_id, bounds)
.on_click::<Editor, _>(
MouseButton::Left,
@ -708,7 +708,7 @@ impl InfoPopover {
);
}
if region.code {
scene.push_quad(gpui::Quad {
cx.scene().push_quad(gpui::Quad {
bounds,
background: Some(code_span_background_color),
border: Default::default(),

View File

@ -16,7 +16,7 @@ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
};
use project::{FormatTrigger, Item as _, Project, ProjectPath};
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view};
use smallvec::SmallVec;
use std::{
@ -26,6 +26,7 @@ use std::{
iter,
ops::Range,
path::{Path, PathBuf},
sync::Arc,
};
use text::Selection;
use util::{
@ -978,7 +979,26 @@ impl SearchableItem for Editor {
}
self.change_selections(None, cx, |s| s.select_ranges(ranges));
}
fn replace(
&mut self,
identifier: &Self::Match,
query: &SearchQuery,
cx: &mut ViewContext<Self>,
) {
let text = self.buffer.read(cx);
let text = text.snapshot(cx);
let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
let text: Cow<_> = if text.len() == 1 {
text.first().cloned().unwrap().into()
} else {
let joined_chunks = text.join("");
joined_chunks.into()
};
if let Some(replacement) = query.replacement(&text) {
self.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
}
}
fn match_index_for_direction(
&mut self,
matches: &Vec<Range<Anchor>>,
@ -1030,7 +1050,7 @@ impl SearchableItem for Editor {
fn find_matches(
&mut self,
query: project::search::SearchQuery,
query: Arc<project::search::SearchQuery>,
cx: &mut ViewContext<Self>,
) -> Task<Vec<Range<Anchor>>> {
let buffer = self.buffer().read(cx).snapshot(cx);

View File

@ -13,7 +13,7 @@ use gpui::{
use isahc::Request;
use language::Buffer;
use postage::prelude::Stream;
use project::Project;
use project::{search::SearchQuery, Project};
use regex::Regex;
use serde::Serialize;
use smallvec::SmallVec;
@ -418,10 +418,13 @@ impl SearchableItem for FeedbackEditor {
self.editor
.update(cx, |e, cx| e.select_matches(matches, cx))
}
fn replace(&mut self, matches: &Self::Match, query: &SearchQuery, cx: &mut ViewContext<Self>) {
self.editor
.update(cx, |e, cx| e.replace(matches, query, cx));
}
fn find_matches(
&mut self,
query: project::search::SearchQuery,
query: Arc<project::search::SearchQuery>,
cx: &mut ViewContext<Self>,
) -> Task<Vec<Self::Match>> {
self.editor

View File

@ -1528,8 +1528,13 @@ mod tests {
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
active_pane
.update(cx, |pane, cx| {
pane.close_active_item(&workspace::CloseActiveItem, cx)
.unwrap()
pane.close_active_item(
&workspace::CloseActiveItem {
save_behavior: None,
},
cx,
)
.unwrap()
})
.await
.unwrap();

View File

@ -48,7 +48,8 @@ serde_derive.workspace = true
serde_json.workspace = true
smallvec.workspace = true
smol.workspace = true
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] }
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
thiserror.workspace = true
time.workspace = true
tiny-skia = "0.5"
usvg = { version = "0.14", features = [] }

View File

@ -42,29 +42,28 @@ impl<V: View> gpui::Element<V> for CornersElement {
&mut self,
constraint: gpui::SizeConstraint,
_: &mut V,
_: &mut gpui::LayoutContext<V>,
_: &mut gpui::ViewContext<V>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
(constraint.max, ())
}
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
_: pathfinder_geometry::rect::RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut gpui::PaintContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> Self::PaintState {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds,
background: Some(Color::white()),
..Default::default()
});
scene.push_layer(None);
cx.scene().push_layer(None);
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
background: Some(Color::red()),
border: Default::default(),
@ -74,7 +73,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
background: Some(Color::green()),
border: Default::default(),
@ -84,7 +83,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
background: Some(Color::blue()),
border: Default::default(),
@ -94,7 +93,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
background: Some(Color::yellow()),
border: Default::default(),
@ -104,7 +103,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.push_shadow(Shadow {
cx.scene().push_shadow(Shadow {
bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
corner_radii: gpui::scene::CornerRadii {
bottom_right: 20.,
@ -114,8 +113,8 @@ impl<V: View> gpui::Element<V> for CornersElement {
color: Color::black(),
});
scene.push_layer(None);
scene.push_quad(Quad {
cx.scene().push_layer(None);
cx.scene().push_quad(Quad {
bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
background: Some(Color::red()),
border: Default::default(),
@ -125,8 +124,8 @@ impl<V: View> gpui::Element<V> for CornersElement {
},
});
scene.pop_layer();
scene.pop_layer();
cx.scene().pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(

View File

@ -62,12 +62,12 @@ impl gpui::View for TextView {
},
)
.with_highlights(vec![(17..26, underline), (34..40, underline)])
.with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, scene, _| {
scene.push_cursor_region(CursorRegion {
.with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, cx| {
cx.scene().push_cursor_region(CursorRegion {
bounds,
style: CursorStyle::PointingHand,
});
scene.push_mouse_region(
cx.scene().push_mouse_region(
MouseRegion::new::<Self>(view_id, ix, bounds).on_click::<Self, _>(
MouseButton::Left,
move |_, _, _| {

View File

@ -1,26 +0,0 @@
[package]
name = "playground"
version = "0.1.0"
edition = "2021"
publish = false
[[bin]]
name = "playground"
path = "src/playground.rs"
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
gpui = { path = ".." }
log.workspace = true
playground_macros = { path = "../playground_macros" }
parking_lot.workspace = true
refineable.workspace = true
serde.workspace = true
simplelog = "0.9"
smallvec.workspace = true
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] }
util = { path = "../../util" }
[dev-dependencies]
gpui = { path = "..", features = ["test-support"] }

View File

@ -1,108 +0,0 @@
use crate::{
element::{AnyElement, Element, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
layout_context::LayoutContext,
paint_context::PaintContext,
style::{Style, StyleHelpers, StyleRefinement, Styleable},
};
use anyhow::Result;
use gpui::LayoutId;
use smallvec::SmallVec;
pub struct Div<V: 'static> {
style: StyleRefinement,
handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>,
}
pub fn div<V>() -> Div<V> {
Div {
style: Default::default(),
handlers: Default::default(),
children: Default::default(),
}
}
impl<V: 'static> Element<V> for Div<V> {
type Layout = ();
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, ()>>
where
Self: Sized,
{
let children = self
.children
.iter_mut()
.map(|child| child.layout(view, cx))
.collect::<Result<Vec<LayoutId>>>()?;
cx.add_layout_node(self.style(), (), children)
}
fn paint(&mut self, view: &mut V, layout: &mut Layout<V, ()>, cx: &mut PaintContext<V>)
where
Self: Sized,
{
let style = self.style();
style.paint_background::<V, Self>(layout, cx);
for child in &mut self.children {
child.paint(view, cx);
}
}
}
impl<V> Styleable for Div<V> {
type Style = Style;
fn declared_style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl<V> StyleHelpers for Div<V> {}
impl<V> Interactive<V> for Div<V> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
&mut self.handlers
}
}
impl<V: 'static> ParentElement<V> for Div<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
#[test]
fn test() {
// let elt = div().w_auto();
}
// trait Element<V: 'static> {
// type Style;
// fn layout()
// }
// trait Stylable<V: 'static>: Element<V> {
// type Style;
// fn with_style(self, style: Self::Style) -> Self;
// }
// pub struct HoverStyle<S> {
// default: S,
// hovered: S,
// }
// struct Hover<V: 'static, C: Stylable<V>> {
// child: C,
// style: HoverStyle<C::Style>,
// }
// impl<V: 'static, C: Stylable<V>> Hover<V, C> {
// fn new(child: C, style: HoverStyle<C::Style>) -> Self {
// Self { child, style }
// }
// }

View File

@ -1,158 +0,0 @@
use anyhow::Result;
use derive_more::{Deref, DerefMut};
use gpui::{geometry::rect::RectF, EngineLayout};
use smallvec::SmallVec;
use std::marker::PhantomData;
use util::ResultExt;
pub use crate::layout_context::LayoutContext;
pub use crate::paint_context::PaintContext;
type LayoutId = gpui::LayoutId;
pub trait Element<V: 'static>: 'static {
type Layout;
fn layout(
&mut self,
view: &mut V,
cx: &mut LayoutContext<V>,
) -> Result<Layout<V, Self::Layout>>
where
Self: Sized;
fn paint(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) where
Self: Sized;
fn into_any(self) -> AnyElement<V>
where
Self: 'static + Sized,
{
AnyElement(Box::new(ElementState {
element: self,
layout: None,
}))
}
}
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
trait ElementStateObject<V> {
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId>;
fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>);
}
/// A wrapper around an element that stores its layout state.
struct ElementState<V: 'static, E: Element<V>> {
element: E,
layout: Option<Layout<V, E::Layout>>,
}
/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
impl<V, E: Element<V>> ElementStateObject<V> for ElementState<V, E> {
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
let layout = self.element.layout(view, cx)?;
let layout_id = layout.id;
self.layout = Some(layout);
Ok(layout_id)
}
fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
let layout = self.layout.as_mut().expect("paint called before layout");
if layout.engine_layout.is_none() {
layout.engine_layout = cx.computed_layout(layout.id).log_err()
}
self.element.paint(view, layout, cx)
}
}
/// A dynamic element.
pub struct AnyElement<V>(Box<dyn ElementStateObject<V>>);
impl<V> AnyElement<V> {
pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
self.0.layout(view, cx)
}
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
self.0.paint(view, cx)
}
}
#[derive(Deref, DerefMut)]
pub struct Layout<V, D> {
id: LayoutId,
engine_layout: Option<EngineLayout>,
#[deref]
#[deref_mut]
element_data: D,
view_type: PhantomData<V>,
}
impl<V: 'static, D> Layout<V, D> {
pub fn new(id: LayoutId, element_data: D) -> Self {
Self {
id,
engine_layout: None,
element_data: element_data,
view_type: PhantomData,
}
}
pub fn bounds(&mut self, cx: &mut PaintContext<V>) -> RectF {
self.engine_layout(cx).bounds
}
pub fn order(&mut self, cx: &mut PaintContext<V>) -> u32 {
self.engine_layout(cx).order
}
fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout {
self.engine_layout
.get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default())
}
}
impl<V: 'static> Layout<V, Option<AnyElement<V>>> {
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
let mut element = self.element_data.take().unwrap();
element.paint(view, cx);
self.element_data = Some(element);
}
}
pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
fn child(mut self, child: impl IntoElement<V>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_element().into_any());
self
}
fn children<I, E>(mut self, children: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoElement<V>,
Self: Sized,
{
self.children_mut().extend(
children
.into_iter()
.map(|child| child.into_element().into_any()),
);
self
}
}
pub trait IntoElement<V: 'static> {
type Element: Element<V>;
fn into_element(self) -> Self::Element;
}

View File

@ -1,76 +0,0 @@
use crate::{
element::{Element, Layout},
layout_context::LayoutContext,
paint_context::PaintContext,
style::{StyleRefinement, Styleable},
};
use anyhow::Result;
use gpui::platform::MouseMovedEvent;
use refineable::Refineable;
use std::{cell::Cell, marker::PhantomData};
pub struct Hoverable<V: 'static, E: Element<V> + Styleable> {
hovered: Cell<bool>,
child_style: StyleRefinement,
hovered_style: StyleRefinement,
child: E,
view_type: PhantomData<V>,
}
pub fn hoverable<V, E: Element<V> + Styleable>(mut child: E) -> Hoverable<V, E> {
Hoverable {
hovered: Cell::new(false),
child_style: child.declared_style().clone(),
hovered_style: Default::default(),
child,
view_type: PhantomData,
}
}
impl<V, E: Element<V> + Styleable> Styleable for Hoverable<V, E> {
type Style = E::Style;
fn declared_style(&mut self) -> &mut crate::style::StyleRefinement {
self.child.declared_style()
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
type Layout = E::Layout;
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, Self::Layout>>
where
Self: Sized,
{
self.child.layout(view, cx)
}
fn paint(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) where
Self: Sized,
{
if self.hovered.get() {
// If hovered, refine the child's style with this element's style.
self.child.declared_style().refine(&self.hovered_style);
} else {
// Otherwise, set the child's style back to its original style.
*self.child.declared_style() = self.child_style.clone();
}
let bounds = layout.bounds(cx);
let order = layout.order(cx);
self.hovered.set(bounds.contains_point(cx.mouse_position()));
let was_hovered = self.hovered.clone();
cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {
let is_hovered = bounds.contains_point(event.position);
if is_hovered != was_hovered.get() {
was_hovered.set(is_hovered);
cx.repaint();
}
});
}
}

View File

@ -1,34 +0,0 @@
use gpui::{platform::MouseMovedEvent, EventContext};
use smallvec::SmallVec;
use std::rc::Rc;
pub trait Interactive<V: 'static> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
fn on_mouse_move<H>(mut self, handler: H) -> Self
where
H: 'static + Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>),
Self: Sized,
{
self.interaction_handlers()
.mouse_moved
.push(Rc::new(move |view, event, hit_test, cx| {
handler(view, event, hit_test, cx);
cx.bubble
}));
self
}
}
pub struct InteractionHandlers<V: 'static> {
mouse_moved:
SmallVec<[Rc<dyn Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>) -> bool>; 2]>,
}
impl<V> Default for InteractionHandlers<V> {
fn default() -> Self {
Self {
mouse_moved: Default::default(),
}
}
}

View File

@ -1,54 +0,0 @@
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
pub use gpui::LayoutContext as LegacyLayoutContext;
use gpui::{RenderContext, ViewContext};
pub use taffy::tree::NodeId;
use crate::{element::Layout, style::Style};
#[derive(Deref, DerefMut)]
pub struct LayoutContext<'a, 'b, 'c, 'd, V> {
#[deref]
#[deref_mut]
pub(crate) legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>,
}
impl<'a, 'b, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, '_, '_, V> {
fn text_style(&self) -> gpui::fonts::TextStyle {
self.legacy_cx.text_style()
}
fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
self.legacy_cx.push_text_style(style)
}
fn pop_text_style(&mut self) {
self.legacy_cx.pop_text_style()
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> {
pub fn new(legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>) -> Self {
Self { legacy_cx }
}
pub fn add_layout_node<D>(
&mut self,
style: Style,
element_data: D,
children: impl IntoIterator<Item = NodeId>,
) -> Result<Layout<V, D>> {
let rem_size = self.rem_pixels();
let id = self
.legacy_cx
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
.add_node(style.to_taffy(rem_size), children)?;
Ok(Layout::new(id, element_data))
}
}

View File

@ -1,71 +0,0 @@
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use gpui::{scene::EventHandler, EngineLayout, EventContext, LayoutId, RenderContext, ViewContext};
pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
use std::{any::TypeId, rc::Rc};
pub use taffy::tree::NodeId;
#[derive(Deref, DerefMut)]
pub struct PaintContext<'a, 'b, 'c, 'd, V> {
#[deref]
#[deref_mut]
pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
pub(crate) scene: &'d mut gpui::SceneBuilder,
}
impl<'a, 'b, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, '_, '_, V> {
fn text_style(&self) -> gpui::fonts::TextStyle {
self.legacy_cx.text_style()
}
fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
self.legacy_cx.push_text_style(style)
}
fn pop_text_style(&mut self) {
self.legacy_cx.pop_text_style()
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
pub fn new(
legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
scene: &'d mut gpui::SceneBuilder,
) -> Self {
Self { legacy_cx, scene }
}
pub fn on_event<E: 'static>(
&mut self,
order: u32,
handler: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
) {
let view = self.weak_handle();
self.scene.event_handlers.push(EventHandler {
order,
handler: Rc::new(move |event, window_cx| {
if let Some(view) = view.upgrade(window_cx) {
view.update(window_cx, |view, view_cx| {
let mut event_cx = EventContext::new(view_cx);
handler(view, event.downcast_ref().unwrap(), &mut event_cx);
event_cx.bubble
})
} else {
true
}
}),
event_type: TypeId::of::<E>(),
})
}
pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<EngineLayout> {
self.layout_engine()
.ok_or_else(|| anyhow!("no layout engine present"))?
.computed_layout(layout_id)
}
}

View File

@ -1,83 +0,0 @@
#![allow(dead_code, unused_variables)]
use crate::{color::black, style::StyleHelpers};
use element::Element;
use gpui::{
geometry::{rect::RectF, vector::vec2f},
platform::WindowOptions,
};
use log::LevelFilter;
use simplelog::SimpleLogger;
use themes::{rose_pine, ThemeColors};
use view::view;
mod adapter;
mod color;
mod components;
mod div;
mod element;
mod hoverable;
mod interactive;
mod layout_context;
mod paint_context;
mod style;
mod text;
mod themes;
mod view;
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
gpui::App::new(()).unwrap().run(|cx| {
cx.add_window(
WindowOptions {
bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
vec2f(0., 0.),
vec2f(400., 300.),
)),
center: true,
..Default::default()
},
|_| view(|_| playground(&rose_pine::moon())),
);
cx.platform().activate(true);
});
}
fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
use div::div;
div()
.text_color(black())
.h_full()
.w_1_2()
.fill(theme.success(0.5))
// .hover()
// .fill(theme.error(0.5))
// .child(button().label("Hello").click(|_, _, _| println!("click!")))
}
// todo!()
// // column()
// // .size(auto())
// // .fill(theme.base(0.5))
// // .text_color(theme.text(0.5))
// // .child(title_bar(theme))
// // .child(stage(theme))
// // .child(status_bar(theme))
// }
// fn title_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
// row()
// .fill(theme.base(0.2))
// .justify(0.)
// .width(auto())
// .child(text("Zed Playground"))
// }
// fn stage<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
// row().fill(theme.surface(0.9))
// }
// fn status_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
// row().fill(theme.surface(0.1))
// }

View File

@ -1,286 +0,0 @@
use crate::{
color::Hsla,
element::{Element, Layout},
paint_context::PaintContext,
};
use gpui::{
fonts::TextStyleRefinement,
geometry::{
AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement,
Size, SizeRefinement,
},
};
use playground_macros::styleable_helpers;
use refineable::Refineable;
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
};
#[derive(Clone, Refineable)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
// Overflow properties
/// How children overflowing their container should affect layout
#[refineable]
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
// Position properties
/// What should the `position` value of this struct use as a base offset?
pub position: Position,
/// How should the position of this element be tweaked relative to the layout defined?
#[refineable]
pub inset: Edges<Length>,
// Size properies
/// Sets the initial size of the item
#[refineable]
pub size: Size<Length>,
/// Controls the minimum size of the item
#[refineable]
pub min_size: Size<Length>,
/// Controls the maximum size of the item
#[refineable]
pub max_size: Size<Length>,
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
pub aspect_ratio: Option<f32>,
// Spacing Properties
/// How large should the margin be on each side?
#[refineable]
pub margin: Edges<Length>,
/// How large should the padding be on each side?
#[refineable]
pub padding: Edges<DefiniteLength>,
/// How large should the border be on each side?
#[refineable]
pub border: Edges<DefiniteLength>,
// Alignment properties
/// How this node's children aligned in the cross/block axis?
pub align_items: Option<AlignItems>,
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
pub align_self: Option<AlignSelf>,
/// How should content contained within this item be aligned in the cross/block axis
pub align_content: Option<AlignContent>,
/// How should contained within this item be aligned in the main/inline axis
pub justify_content: Option<JustifyContent>,
/// How large should the gaps between items in a flex container be?
#[refineable]
pub gap: Size<DefiniteLength>,
// Flexbox properies
/// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
/// Should elements wrap, or stay in a single line?
pub flex_wrap: FlexWrap,
/// Sets the initial main axis size of the item
pub flex_basis: Length,
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
pub flex_grow: f32,
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
pub flex_shrink: f32,
/// The fill color of this element
pub fill: Option<Fill>,
/// The radius of the corners of this element
#[refineable]
pub corner_radii: CornerRadii,
/// The color of text within this element. Cascades to children unless overridden.
pub text_color: Option<Hsla>,
}
impl Style {
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
taffy::style::Style {
display: self.display,
overflow: self.overflow.clone().into(),
scrollbar_width: self.scrollbar_width,
position: self.position,
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
max_size: self.max_size.to_taffy(rem_size),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border.to_taffy(rem_size),
align_items: self.align_items,
align_self: self.align_self,
align_content: self.align_content,
justify_content: self.justify_content,
gap: self.gap.to_taffy(rem_size),
flex_direction: self.flex_direction,
flex_wrap: self.flex_wrap,
flex_basis: self.flex_basis.to_taffy(rem_size).into(),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
}
}
/// Paints the background of an element styled with this style.
/// Return the bounds in which to paint the content.
pub fn paint_background<V: 'static, E: Element<V>>(
&self,
layout: &mut Layout<V, E::Layout>,
cx: &mut PaintContext<V>,
) {
let bounds = layout.bounds(cx);
let rem_size = cx.rem_pixels();
if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
cx.scene.push_quad(gpui::Quad {
bounds,
background: Some(color.into()),
corner_radii: self.corner_radii.to_gpui(rem_size),
border: Default::default(),
});
}
}
}
impl Default for Style {
fn default() -> Self {
Style {
display: Display::DEFAULT,
overflow: Point {
x: Overflow::Visible,
y: Overflow::Visible,
},
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),
padding: Edges::<DefiniteLength>::zero(),
border: Edges::<DefiniteLength>::zero(),
size: Size::auto(),
min_size: Size::auto(),
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::zero(),
// Aligment
align_items: None,
align_self: None,
align_content: None,
justify_content: None,
// Flexbox
flex_direction: FlexDirection::Row,
flex_wrap: FlexWrap::NoWrap,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Length::Auto,
fill: None,
text_color: None,
corner_radii: CornerRadii::default(),
}
}
}
impl StyleRefinement {
pub fn text_style(&self) -> Option<TextStyleRefinement> {
self.text_color.map(|color| TextStyleRefinement {
color: Some(color.into()),
..Default::default()
})
}
}
pub struct OptionalTextStyle {
color: Option<Hsla>,
}
impl OptionalTextStyle {
pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
if let Some(color) = self.color {
style.color = color.into();
}
}
}
#[derive(Clone)]
pub enum Fill {
Color(Hsla),
}
impl Fill {
pub fn color(&self) -> Option<Hsla> {
match self {
Fill::Color(color) => Some(*color),
}
}
}
impl Default for Fill {
fn default() -> Self {
Self::Color(Hsla::default())
}
}
impl From<Hsla> for Fill {
fn from(color: Hsla) -> Self {
Self::Color(color)
}
}
#[derive(Clone, Refineable, Default)]
pub struct CornerRadii {
top_left: AbsoluteLength,
top_right: AbsoluteLength,
bottom_left: AbsoluteLength,
bottom_right: AbsoluteLength,
}
impl CornerRadii {
pub fn to_gpui(&self, rem_size: f32) -> gpui::scene::CornerRadii {
gpui::scene::CornerRadii {
top_left: self.top_left.to_pixels(rem_size),
top_right: self.top_right.to_pixels(rem_size),
bottom_left: self.bottom_left.to_pixels(rem_size),
bottom_right: self.bottom_right.to_pixels(rem_size),
}
}
}
pub trait Styleable {
type Style: refineable::Refineable;
fn declared_style(&mut self) -> &mut playground::style::StyleRefinement;
fn style(&mut self) -> playground::style::Style {
let mut style = playground::style::Style::default();
style.refine(self.declared_style());
style
}
}
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
//
// Example:
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
// fn p_2(mut self) -> Self where Self: Sized;
use crate as playground; // Macro invocation references this crate as playground.
pub trait StyleHelpers: Styleable<Style = Style> {
styleable_helpers!();
fn fill<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.declared_style().fill = Some(fill.into());
self
}
fn text_color<C>(mut self, color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().text_color = Some(color.into());
self
}
}

View File

@ -1,151 +0,0 @@
use crate::{
element::{Element, IntoElement, Layout},
layout_context::LayoutContext,
paint_context::PaintContext,
};
use anyhow::Result;
use gpui::text_layout::LineLayout;
use parking_lot::Mutex;
use std::sync::Arc;
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
type Element = Text;
fn into_element(self) -> Self::Element {
Text { text: self.into() }
}
}
pub struct Text {
text: ArcCow<'static, str>,
}
impl<V: 'static> Element<V> for Text {
type Layout = Arc<Mutex<Option<TextLayout>>>;
fn layout(
&mut self,
view: &mut V,
cx: &mut LayoutContext<V>,
) -> Result<Layout<V, Self::Layout>> {
// let rem_size = cx.rem_pixels();
// let fonts = cx.platform().fonts();
// let text_style = cx.text_style();
// let line_height = cx.font_cache().line_height(text_style.font_size);
// let layout_engine = cx.layout_engine().expect("no layout engine present");
// let text = self.text.clone();
// let layout = Arc::new(Mutex::new(None));
// let style: Style = Style::default().refined(&self.metadata.style);
// let node_id = layout_engine.add_measured_node(style.to_taffy(rem_size), {
// let layout = layout.clone();
// move |params| {
// let line_layout = fonts.layout_line(
// text.as_ref(),
// text_style.font_size,
// &[(text.len(), text_style.to_run())],
// );
// let size = Size {
// width: line_layout.width,
// height: line_height,
// };
// layout.lock().replace(TextLayout {
// line_layout: Arc::new(line_layout),
// line_height,
// });
// size
// }
// })?;
// Ok((node_id, layout))
todo!()
}
fn paint<'a>(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) {
// ) {
// let element_layout_lock = layout.from_element.lock();
// let element_layout = element_layout_lock
// .as_ref()
// .expect("layout has not been performed");
// let line_layout = element_layout.line_layout.clone();
// let line_height = element_layout.line_height;
// drop(element_layout_lock);
// let text_style = cx.text_style();
// let line =
// gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
// line.paint(
// cx.scene,
// layout.from_engine.bounds.origin(),
// layout.from_engine.bounds,
// line_height,
// cx.legacy_cx,
// );
todo!()
}
}
pub struct TextLayout {
line_layout: Arc<LineLayout>,
line_height: f32,
}
pub enum ArcCow<'a, T: ?Sized> {
Borrowed(&'a T),
Owned(Arc<T>),
}
impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
fn clone(&self) -> Self {
match self {
Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
Self::Owned(owned) => Self::Owned(owned.clone()),
}
}
}
impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
fn from(s: &'a T) -> Self {
Self::Borrowed(s)
}
}
impl<T> From<Arc<T>> for ArcCow<'_, T> {
fn from(s: Arc<T>) -> Self {
Self::Owned(s)
}
}
impl From<String> for ArcCow<'_, str> {
fn from(value: String) -> Self {
Self::Owned(value.into())
}
}
impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
ArcCow::Borrowed(s) => s,
ArcCow::Owned(s) => s.as_ref(),
}
}
}
impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
fn as_ref(&self) -> &T {
match self {
ArcCow::Borrowed(borrowed) => borrowed,
ArcCow::Owned(owned) => owned.as_ref(),
}
}
}

View File

@ -1,84 +0,0 @@
use crate::color::{Hsla, Lerp};
use std::ops::Range;
pub mod rose_pine;
pub struct ThemeColors {
pub base: Range<Hsla>,
pub surface: Range<Hsla>,
pub overlay: Range<Hsla>,
pub muted: Range<Hsla>,
pub subtle: Range<Hsla>,
pub text: Range<Hsla>,
pub highlight_low: Range<Hsla>,
pub highlight_med: Range<Hsla>,
pub highlight_high: Range<Hsla>,
pub success: Range<Hsla>,
pub warning: Range<Hsla>,
pub error: Range<Hsla>,
pub inserted: Range<Hsla>,
pub deleted: Range<Hsla>,
pub modified: Range<Hsla>,
}
impl ThemeColors {
pub fn base(&self, level: f32) -> Hsla {
self.base.lerp(level)
}
pub fn surface(&self, level: f32) -> Hsla {
self.surface.lerp(level)
}
pub fn overlay(&self, level: f32) -> Hsla {
self.overlay.lerp(level)
}
pub fn muted(&self, level: f32) -> Hsla {
self.muted.lerp(level)
}
pub fn subtle(&self, level: f32) -> Hsla {
self.subtle.lerp(level)
}
pub fn text(&self, level: f32) -> Hsla {
self.text.lerp(level)
}
pub fn highlight_low(&self, level: f32) -> Hsla {
self.highlight_low.lerp(level)
}
pub fn highlight_med(&self, level: f32) -> Hsla {
self.highlight_med.lerp(level)
}
pub fn highlight_high(&self, level: f32) -> Hsla {
self.highlight_high.lerp(level)
}
pub fn success(&self, level: f32) -> Hsla {
self.success.lerp(level)
}
pub fn warning(&self, level: f32) -> Hsla {
self.warning.lerp(level)
}
pub fn error(&self, level: f32) -> Hsla {
self.error.lerp(level)
}
pub fn inserted(&self, level: f32) -> Hsla {
self.inserted.lerp(level)
}
pub fn deleted(&self, level: f32) -> Hsla {
self.deleted.lerp(level)
}
pub fn modified(&self, level: f32) -> Hsla {
self.modified.lerp(level)
}
}

View File

@ -1,133 +0,0 @@
use std::ops::Range;
use crate::{
color::{hsla, rgb, Hsla},
ThemeColors,
};
pub struct RosePineThemes {
pub default: RosePinePalette,
pub dawn: RosePinePalette,
pub moon: RosePinePalette,
}
#[derive(Clone, Copy, Debug)]
pub struct RosePinePalette {
pub base: Hsla,
pub surface: Hsla,
pub overlay: Hsla,
pub muted: Hsla,
pub subtle: Hsla,
pub text: Hsla,
pub love: Hsla,
pub gold: Hsla,
pub rose: Hsla,
pub pine: Hsla,
pub foam: Hsla,
pub iris: Hsla,
pub highlight_low: Hsla,
pub highlight_med: Hsla,
pub highlight_high: Hsla,
}
impl RosePinePalette {
pub fn default() -> RosePinePalette {
RosePinePalette {
base: rgb(0x191724),
surface: rgb(0x1f1d2e),
overlay: rgb(0x26233a),
muted: rgb(0x6e6a86),
subtle: rgb(0x908caa),
text: rgb(0xe0def4),
love: rgb(0xeb6f92),
gold: rgb(0xf6c177),
rose: rgb(0xebbcba),
pine: rgb(0x31748f),
foam: rgb(0x9ccfd8),
iris: rgb(0xc4a7e7),
highlight_low: rgb(0x21202e),
highlight_med: rgb(0x403d52),
highlight_high: rgb(0x524f67),
}
}
pub fn moon() -> RosePinePalette {
RosePinePalette {
base: rgb(0x232136),
surface: rgb(0x2a273f),
overlay: rgb(0x393552),
muted: rgb(0x6e6a86),
subtle: rgb(0x908caa),
text: rgb(0xe0def4),
love: rgb(0xeb6f92),
gold: rgb(0xf6c177),
rose: rgb(0xea9a97),
pine: rgb(0x3e8fb0),
foam: rgb(0x9ccfd8),
iris: rgb(0xc4a7e7),
highlight_low: rgb(0x2a283e),
highlight_med: rgb(0x44415a),
highlight_high: rgb(0x56526e),
}
}
pub fn dawn() -> RosePinePalette {
RosePinePalette {
base: rgb(0xfaf4ed),
surface: rgb(0xfffaf3),
overlay: rgb(0xf2e9e1),
muted: rgb(0x9893a5),
subtle: rgb(0x797593),
text: rgb(0x575279),
love: rgb(0xb4637a),
gold: rgb(0xea9d34),
rose: rgb(0xd7827e),
pine: rgb(0x286983),
foam: rgb(0x56949f),
iris: rgb(0x907aa9),
highlight_low: rgb(0xf4ede8),
highlight_med: rgb(0xdfdad9),
highlight_high: rgb(0xcecacd),
}
}
}
pub fn default() -> ThemeColors {
theme_colors(&RosePinePalette::default())
}
pub fn moon() -> ThemeColors {
theme_colors(&RosePinePalette::moon())
}
pub fn dawn() -> ThemeColors {
theme_colors(&RosePinePalette::dawn())
}
fn theme_colors(p: &RosePinePalette) -> ThemeColors {
ThemeColors {
base: scale_sl(p.base, (0.8, 0.8), (1.2, 1.2)),
surface: scale_sl(p.surface, (0.8, 0.8), (1.2, 1.2)),
overlay: scale_sl(p.overlay, (0.8, 0.8), (1.2, 1.2)),
muted: scale_sl(p.muted, (0.8, 0.8), (1.2, 1.2)),
subtle: scale_sl(p.subtle, (0.8, 0.8), (1.2, 1.2)),
text: scale_sl(p.text, (0.8, 0.8), (1.2, 1.2)),
highlight_low: scale_sl(p.highlight_low, (0.8, 0.8), (1.2, 1.2)),
highlight_med: scale_sl(p.highlight_med, (0.8, 0.8), (1.2, 1.2)),
highlight_high: scale_sl(p.highlight_high, (0.8, 0.8), (1.2, 1.2)),
success: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
warning: scale_sl(p.gold, (0.8, 0.8), (1.2, 1.2)),
error: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
inserted: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
deleted: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
modified: scale_sl(p.rose, (0.8, 0.8), (1.2, 1.2)),
}
}
/// Produces a range by multiplying the saturation and lightness of the base color by the given
/// start and end factors.
fn scale_sl(base: Hsla, (start_s, start_l): (f32, f32), (end_s, end_l): (f32, f32)) -> Range<Hsla> {
let start = hsla(base.h, base.s * start_s, base.l * start_l, base.a);
let end = hsla(base.h, base.s * end_s, base.l * end_l, base.a);
Range { start, end }
}

View File

@ -1,147 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct StyleableMacroInput;
impl Parse for StyleableMacroInput {
fn parse(_input: ParseStream) -> Result<Self> {
Ok(StyleableMacroInput)
}
}
pub fn styleable_helpers(input: TokenStream) -> TokenStream {
let _ = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_methods();
let output = quote! {
#(#methods)*
};
output.into()
}
fn generate_methods() -> Vec<TokenStream2> {
let mut methods = Vec::new();
for (prefix, auto_allowed, fields) in tailwind_prefixes() {
for (suffix, length_tokens) in tailwind_lengths() {
if !auto_allowed && suffix == "auto" {
// Conditional to skip "auto"
continue;
}
let method_name = format_ident!("{}_{}", prefix, suffix);
let field_assignments = fields
.iter()
.map(|field_tokens| {
quote! {
style.#field_tokens = Some(gpui::geometry::#length_tokens);
}
})
.collect::<Vec<_>>();
let method = quote! {
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
let mut style = self.declared_style();
#(#field_assignments)*
self
}
};
methods.push(method);
}
}
methods
}
fn tailwind_lengths() -> Vec<(&'static str, TokenStream2)> {
vec![
("0", quote! { pixels(0.) }),
("1", quote! { rems(0.25) }),
("2", quote! { rems(0.5) }),
("3", quote! { rems(0.75) }),
("4", quote! { rems(1.) }),
("5", quote! { rems(1.25) }),
("6", quote! { rems(1.5) }),
("8", quote! { rems(2.0) }),
("10", quote! { rems(2.5) }),
("12", quote! { rems(3.) }),
("16", quote! { rems(4.) }),
("20", quote! { rems(5.) }),
("24", quote! { rems(6.) }),
("32", quote! { rems(8.) }),
("40", quote! { rems(10.) }),
("48", quote! { rems(12.) }),
("56", quote! { rems(14.) }),
("64", quote! { rems(16.) }),
("72", quote! { rems(18.) }),
("80", quote! { rems(20.) }),
("96", quote! { rems(24.) }),
("auto", quote! { auto() }),
("px", quote! { pixels(1.) }),
("full", quote! { relative(1.) }),
("1_2", quote! { relative(0.5) }),
("1_3", quote! { relative(1./3.) }),
("2_3", quote! { relative(2./3.) }),
("1_4", quote! { relative(0.25) }),
("2_4", quote! { relative(0.5) }),
("3_4", quote! { relative(0.75) }),
("1_5", quote! { relative(0.2) }),
("2_5", quote! { relative(0.4) }),
("3_5", quote! { relative(0.6) }),
("4_5", quote! { relative(0.8) }),
("1_6", quote! { relative(1./6.) }),
("5_6", quote! { relative(5./6.) }),
("1_12", quote! { relative(1./12.) }),
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
]
}
fn tailwind_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
vec![
("w", true, vec![quote! { size.width }]),
("h", true, vec![quote! { size.height }]),
("min_w", false, vec![quote! { min_size.width }]),
("min_h", false, vec![quote! { min_size.height }]),
("max_w", false, vec![quote! { max_size.width }]),
("max_h", false, vec![quote! { max_size.height }]),
(
"m",
true,
vec![quote! { margin.top }, quote! { margin.bottom }],
),
("mt", true, vec![quote! { margin.top }]),
("mb", true, vec![quote! { margin.bottom }]),
(
"mx",
true,
vec![quote! { margin.left }, quote! { margin.right }],
),
("ml", true, vec![quote! { margin.left }]),
("mr", true, vec![quote! { margin.right }]),
(
"p",
false,
vec![quote! { padding.top }, quote! { padding.bottom }],
),
("pt", false, vec![quote! { padding.top }]),
("pb", false, vec![quote! { padding.bottom }]),
(
"px",
false,
vec![quote! { padding.left }, quote! { padding.right }],
),
("pl", false, vec![quote! { padding.left }]),
("pr", false, vec![quote! { padding.right }]),
("top", true, vec![quote! { inset.top }]),
("bottom", true, vec![quote! { inset.bottom }]),
("left", true, vec![quote! { inset.left }]),
("right", true, vec![quote! { inset.right }]),
]
}

View File

@ -1,99 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, FnArg, ItemFn, PatType};
pub fn tailwind_lengths(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_function = parse_macro_input!(item as ItemFn);
let visibility = &input_function.vis;
let function_signature = input_function.sig.clone();
let function_body = input_function.block;
let where_clause = &function_signature.generics.where_clause;
let argument_name = match function_signature.inputs.iter().nth(1) {
Some(FnArg::Typed(PatType { pat, .. })) => pat,
_ => panic!("Couldn't find the second argument in the function signature"),
};
let mut output_functions = TokenStream2::new();
for (length, value) in fixed_lengths() {
let function_name = format_ident!("{}{}", function_signature.ident, length);
output_functions.extend(quote! {
#visibility fn #function_name(mut self) -> Self #where_clause {
let #argument_name = #value.into();
#function_body
}
});
}
output_functions.into()
}
fn fixed_lengths() -> Vec<(&'static str, TokenStream2)> {
vec![
("0", quote! { DefinedLength::Pixels(0.) }),
("px", quote! { DefinedLength::Pixels(1.) }),
("0_5", quote! { DefinedLength::Rems(0.125) }),
("1", quote! { DefinedLength::Rems(0.25) }),
("1_5", quote! { DefinedLength::Rems(0.375) }),
("2", quote! { DefinedLength::Rems(0.5) }),
("2_5", quote! { DefinedLength::Rems(0.625) }),
("3", quote! { DefinedLength::Rems(0.75) }),
("3_5", quote! { DefinedLength::Rems(0.875) }),
("4", quote! { DefinedLength::Rems(1.) }),
("5", quote! { DefinedLength::Rems(1.25) }),
("6", quote! { DefinedLength::Rems(1.5) }),
("7", quote! { DefinedLength::Rems(1.75) }),
("8", quote! { DefinedLength::Rems(2.) }),
("9", quote! { DefinedLength::Rems(2.25) }),
("10", quote! { DefinedLength::Rems(2.5) }),
("11", quote! { DefinedLength::Rems(2.75) }),
("12", quote! { DefinedLength::Rems(3.) }),
("14", quote! { DefinedLength::Rems(3.5) }),
("16", quote! { DefinedLength::Rems(4.) }),
("20", quote! { DefinedLength::Rems(5.) }),
("24", quote! { DefinedLength::Rems(6.) }),
("28", quote! { DefinedLength::Rems(7.) }),
("32", quote! { DefinedLength::Rems(8.) }),
("36", quote! { DefinedLength::Rems(9.) }),
("40", quote! { DefinedLength::Rems(10.) }),
("44", quote! { DefinedLength::Rems(11.) }),
("48", quote! { DefinedLength::Rems(12.) }),
("52", quote! { DefinedLength::Rems(13.) }),
("56", quote! { DefinedLength::Rems(14.) }),
("60", quote! { DefinedLength::Rems(15.) }),
("64", quote! { DefinedLength::Rems(16.) }),
("72", quote! { DefinedLength::Rems(18.) }),
("80", quote! { DefinedLength::Rems(20.) }),
("96", quote! { DefinedLength::Rems(24.) }),
("half", quote! { DefinedLength::Percent(50.) }),
("1_3rd", quote! { DefinedLength::Percent(33.333333) }),
("2_3rd", quote! { DefinedLength::Percent(66.666667) }),
("1_4th", quote! { DefinedLength::Percent(25.) }),
("2_4th", quote! { DefinedLength::Percent(50.) }),
("3_4th", quote! { DefinedLength::Percent(75.) }),
("1_5th", quote! { DefinedLength::Percent(20.) }),
("2_5th", quote! { DefinedLength::Percent(40.) }),
("3_5th", quote! { DefinedLength::Percent(60.) }),
("4_5th", quote! { DefinedLength::Percent(80.) }),
("1_6th", quote! { DefinedLength::Percent(16.666667) }),
("2_6th", quote! { DefinedLength::Percent(33.333333) }),
("3_6th", quote! { DefinedLength::Percent(50.) }),
("4_6th", quote! { DefinedLength::Percent(66.666667) }),
("5_6th", quote! { DefinedLength::Percent(83.333333) }),
("1_12th", quote! { DefinedLength::Percent(8.333333) }),
("2_12th", quote! { DefinedLength::Percent(16.666667) }),
("3_12th", quote! { DefinedLength::Percent(25.) }),
("4_12th", quote! { DefinedLength::Percent(33.333333) }),
("5_12th", quote! { DefinedLength::Percent(41.666667) }),
("6_12th", quote! { DefinedLength::Percent(50.) }),
("7_12th", quote! { DefinedLength::Percent(58.333333) }),
("8_12th", quote! { DefinedLength::Percent(66.666667) }),
("9_12th", quote! { DefinedLength::Percent(75.) }),
("10_12th", quote! { DefinedLength::Percent(83.333333) }),
("11_12th", quote! { DefinedLength::Percent(91.666667) }),
("full", quote! { DefinedLength::Percent(100.) }),
]
}

View File

@ -10,7 +10,7 @@ mod window_input_handler;
use crate::{
elements::{AnyElement, AnyRootElement, RootElement},
executor::{self, Task},
fonts::TextStyle,
image_cache::ImageCache,
json,
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
platform::{
@ -28,6 +28,7 @@ use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
use derive_more::Deref;
pub use menu::*;
use parking_lot::Mutex;
use pathfinder_geometry::rect::RectF;
use platform::Event;
use postage::oneshot;
#[cfg(any(test, feature = "test-support"))]
@ -51,8 +52,12 @@ use std::{
};
#[cfg(any(test, feature = "test-support"))]
pub use test_app_context::{ContextHandle, TestAppContext};
use util::ResultExt;
use util::{
http::{self, HttpClient},
ResultExt,
};
use uuid::Uuid;
pub use window::MeasureParams;
use window_input_handler::WindowInputHandler;
pub trait Entity: 'static {
@ -154,12 +159,14 @@ impl App {
let platform = platform::current::platform();
let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
let foreground_platform = platform::current::foreground_platform(foreground.clone());
let http_client = http::client();
let app = Self(Rc::new(RefCell::new(AppContext::new(
foreground,
Arc::new(executor::Background::new()),
platform.clone(),
foreground_platform.clone(),
Arc::new(FontCache::new(platform.fonts())),
http_client,
Default::default(),
asset_source,
))));
@ -456,6 +463,7 @@ pub struct AppContext {
pub asset_cache: Arc<AssetCache>,
font_system: Arc<dyn FontSystem>,
pub font_cache: Arc<FontCache>,
pub image_cache: Arc<ImageCache>,
action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
// Entity Types -> { Action Types -> Action Handlers }
@ -499,6 +507,7 @@ impl AppContext {
platform: Arc<dyn platform::Platform>,
foreground_platform: Rc<dyn platform::ForegroundPlatform>,
font_cache: Arc<FontCache>,
http_client: Arc<dyn HttpClient>,
ref_counts: RefCounts,
asset_source: impl AssetSource,
) -> Self {
@ -517,6 +526,7 @@ impl AppContext {
platform,
foreground_platform,
font_cache,
image_cache: Arc::new(ImageCache::new(http_client)),
asset_cache: Arc::new(AssetCache::new(asset_source)),
action_deserializers: Default::default(),
capture_actions: Default::default(),
@ -1898,7 +1908,6 @@ impl AppContext {
fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) {
self.update_window(window, |cx| {
cx.layout(false).log_err();
if let Some(scene) = cx.paint().log_err() {
cx.window.platform_window.present_scene(scene);
}
@ -3345,10 +3354,6 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
self.element_state::<Tag, T>(element_id, T::default())
}
pub fn rem_pixels(&self) -> f32 {
16.
}
pub fn default_element_state_dynamic<T: 'static + Default>(
&mut self,
tag: TypeTag,
@ -3356,6 +3361,59 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
) -> ElementStateHandle<T> {
self.element_state_dynamic::<T>(tag, element_id, T::default())
}
/// Return keystrokes that would dispatch the given action on the given view.
pub(crate) fn keystrokes_for_action(
&mut self,
view_id: usize,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.notify_if_view_ancestors_change(view_id);
let window = self.window_handle;
let mut contexts = Vec::new();
let mut handler_depth = None;
for (i, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action.id()) {
handler_depth = Some(i);
}
}
contexts.push(view_metadata.keymap_context.clone());
}
}
if self.global_actions.contains_key(&action.id()) {
handler_depth = Some(contexts.len())
}
let handler_depth = handler_depth.unwrap_or(0);
(0..=handler_depth).find_map(|depth| {
let contexts = &contexts[depth..];
self.keystroke_matcher
.keystrokes_for_action(action, contexts)
})
}
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
let self_view_id = self.view_id;
self.window
.views_to_notify_if_ancestors_change
.entry(view_id)
.or_default()
.push(self_view_id);
}
pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
self.scene().push_layer(clip_bounds);
let result = f(self);
self.scene().pop_layer();
result
}
}
impl<V: View> ViewContext<'_, '_, V> {
@ -3447,265 +3505,6 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
}
}
/// Methods shared by both LayoutContext and PaintContext
///
/// It's that PaintContext should be implemented in terms of layout context and
/// deref to it, in which case we wouldn't need this.
pub trait RenderContext<'a, 'b, V> {
fn text_style(&self) -> TextStyle;
fn push_text_style(&mut self, style: TextStyle);
fn pop_text_style(&mut self);
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V>;
}
pub struct LayoutContext<'a, 'b, 'c, V> {
// Nathan: Making this is public while I work on playground.
pub view_context: &'c mut ViewContext<'a, 'b, V>,
new_parents: &'c mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
text_style_stack: Vec<TextStyle>,
pub refreshing: bool,
}
impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
pub fn new(
view_context: &'c mut ViewContext<'a, 'b, V>,
new_parents: &'c mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
refreshing: bool,
) -> Self {
Self {
view_context,
new_parents,
views_to_notify_if_ancestors_change,
text_style_stack: Vec::new(),
refreshing,
}
}
pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
self.view_context
}
/// Return keystrokes that would dispatch the given action on the given view.
pub(crate) fn keystrokes_for_action(
&mut self,
view_id: usize,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.notify_if_view_ancestors_change(view_id);
let window = self.window_handle;
let mut contexts = Vec::new();
let mut handler_depth = None;
for (i, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(window, view_id)) {
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
if actions.contains_key(&action.id()) {
handler_depth = Some(i);
}
}
contexts.push(view_metadata.keymap_context.clone());
}
}
if self.global_actions.contains_key(&action.id()) {
handler_depth = Some(contexts.len())
}
let handler_depth = handler_depth.unwrap_or(0);
(0..=handler_depth).find_map(|depth| {
let contexts = &contexts[depth..];
self.keystroke_matcher
.keystrokes_for_action(action, contexts)
})
}
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
let self_view_id = self.view_id;
self.views_to_notify_if_ancestors_change
.entry(view_id)
.or_default()
.push(self_view_id);
}
pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
where
F: FnOnce(&mut Self) -> T,
{
self.push_text_style(style);
let result = f(self);
self.pop_text_style();
result
}
}
impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, 'c, V> {
fn text_style(&self) -> TextStyle {
self.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
fn push_text_style(&mut self, style: TextStyle) {
self.text_style_stack.push(style);
}
fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
fn deref(&self) -> &Self::Target {
&self.view_context
}
}
impl<V> DerefMut for LayoutContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
BorrowAppContext::read_with(&*self.view_context, f)
}
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
BorrowAppContext::update(&mut *self.view_context, f)
}
}
impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
BorrowWindowContext::read_window(&*self.view_context, window, f)
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window: AnyWindowHandle,
f: F,
) -> T {
BorrowWindowContext::update_window(&mut *self.view_context, window, f)
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f)
}
}
pub struct PaintContext<'a, 'b, 'c, V> {
pub view_context: &'c mut ViewContext<'a, 'b, V>,
text_style_stack: Vec<TextStyle>,
}
impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
Self {
view_context,
text_style_stack: Vec::new(),
}
}
}
impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, 'c, V> {
fn text_style(&self) -> TextStyle {
self.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
fn push_text_style(&mut self, style: TextStyle) {
self.text_style_stack.push(style);
}
fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
&mut self.view_context
}
}
impl<'a, 'b, 'c, V> Deref for PaintContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
fn deref(&self) -> &Self::Target {
&self.view_context
}
}
impl<V> DerefMut for PaintContext<'_, '_, '_, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view_context
}
}
impl<V> BorrowAppContext for PaintContext<'_, '_, '_, V> {
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
BorrowAppContext::read_with(&*self.view_context, f)
}
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
BorrowAppContext::update(&mut *self.view_context, f)
}
}
impl<V> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
type Result<T> = T;
fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
where
F: FnOnce(&WindowContext) -> T,
{
BorrowWindowContext::read_window(self.view_context, window, f)
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window_optional(self.view_context, window, f)
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
where
F: FnOnce(&mut WindowContext) -> T,
{
BorrowWindowContext::update_window(self.view_context, window, f)
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window_optional(self.view_context, window, f)
}
}
pub struct EventContext<'a, 'b, 'c, V> {
view_context: &'c mut ViewContext<'a, 'b, V>,
pub(crate) handled: bool,
@ -6555,32 +6354,21 @@ mod tests {
view_1.update(cx, |_, cx| {
view_2.update(cx, |_, cx| {
// Sanity check
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
assert_eq!(
layout_cx
.keystrokes_for_action(view_1_id, &Action1)
cx.keystrokes_for_action(view_1_id, &Action1)
.unwrap()
.as_slice(),
&[Keystroke::parse("a").unwrap()]
);
assert_eq!(
layout_cx
.keystrokes_for_action(view_2.id(), &Action2)
cx.keystrokes_for_action(view_2.id(), &Action2)
.unwrap()
.as_slice(),
&[Keystroke::parse("b").unwrap()]
);
assert_eq!(layout_cx.keystrokes_for_action(view_1.id(), &Action3), None);
assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None);
assert_eq!(
layout_cx
.keystrokes_for_action(view_2.id(), &Action3)
cx.keystrokes_for_action(view_2.id(), &Action3)
.unwrap()
.as_slice(),
&[Keystroke::parse("c").unwrap()]
@ -6589,21 +6377,17 @@ mod tests {
// The 'a' keystroke propagates up the view tree from view_2
// to view_1. The action, Action1, is handled by view_1.
assert_eq!(
layout_cx
.keystrokes_for_action(view_2.id(), &Action1)
cx.keystrokes_for_action(view_2.id(), &Action1)
.unwrap()
.as_slice(),
&[Keystroke::parse("a").unwrap()]
);
// Actions that are handled below the current view don't have bindings
assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None);
assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None);
// Actions that are handled in other branches of the tree should not have a binding
assert_eq!(
layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction),
None
);
assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
});
});

View File

@ -57,6 +57,7 @@ impl TestAppContext {
platform,
foreground_platform.clone(),
font_cache,
util::http::FakeHttpClient::with_404_response(),
RefCounts::new(leak_detector),
(),
);

View File

@ -1,5 +1,6 @@
use crate::{
elements::AnyRootElement,
fonts::{TextStyle, TextStyleRefinement},
geometry::{rect::RectF, Size},
json::ToJson,
keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
@ -15,9 +16,8 @@ use crate::{
text_layout::TextLayoutCache,
util::post_inc,
Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
WindowInvalidation,
BorrowWindowContext, Effect, Element, Entity, Handle, MouseRegion, MouseRegionId, SceneBuilder,
Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@ -30,7 +30,7 @@ use sqlez::{
statement::Statement,
};
use std::{
any::TypeId,
any::{type_name, Any, TypeId},
mem,
ops::{Deref, DerefMut, Range, Sub},
};
@ -50,20 +50,28 @@ pub struct Window {
pub(crate) parents: HashMap<usize, usize>,
pub(crate) is_active: bool,
pub(crate) is_fullscreen: bool,
inspector_enabled: bool,
pub(crate) invalidation: Option<WindowInvalidation>,
pub(crate) platform_window: Box<dyn platform::Window>,
pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
scene: SceneBuilder,
pub(crate) text_style_stack: Vec<TextStyle>,
pub(crate) theme_stack: Vec<Box<dyn Any>>,
pub(crate) new_parents: HashMap<usize, usize>,
pub(crate) views_to_notify_if_ancestors_change: HashMap<usize, SmallVec<[usize; 2]>>,
titlebar_height: f32,
appearance: Appearance,
cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<(MouseRegion, usize)>,
event_handlers: Vec<EventHandler>,
last_mouse_moved_event: Option<Event>,
last_mouse_position: Vector2F,
pressed_buttons: HashSet<MouseButton>,
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
mouse_position: Vector2F,
text_layout_cache: TextLayoutCache,
refreshing: bool,
}
impl Window {
@ -87,19 +95,27 @@ impl Window {
is_active: false,
invalidation: None,
is_fullscreen: false,
inspector_enabled: false,
platform_window,
rendered_views: Default::default(),
scene: SceneBuilder::new(),
text_style_stack: Vec::new(),
theme_stack: Vec::new(),
new_parents: HashMap::default(),
views_to_notify_if_ancestors_change: HashMap::default(),
cursor_regions: Default::default(),
mouse_regions: Default::default(),
event_handlers: Default::default(),
text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
last_mouse_moved_event: None,
last_mouse_position: Vector2F::zero(),
pressed_buttons: Default::default(),
hovered_region_ids: Default::default(),
clicked_region_ids: Default::default(),
clicked_region: None,
mouse_position: vec2f(0., 0.),
titlebar_height,
appearance,
refreshing: false,
};
let mut window_context = WindowContext::mutable(cx, &mut window, handle);
@ -226,6 +242,26 @@ impl<'a> WindowContext<'a> {
.push_back(Effect::RepaintWindow { window });
}
pub fn scene(&mut self) -> &mut SceneBuilder {
&mut self.window.scene
}
pub fn enable_inspector(&mut self) {
self.window.inspector_enabled = true;
}
pub fn is_inspector_enabled(&self) -> bool {
self.window.inspector_enabled
}
pub fn is_mouse_down(&self, button: MouseButton) -> bool {
self.window.pressed_buttons.contains(&button)
}
pub fn rem_size(&self) -> f32 {
16.
}
pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
self.window.layout_engines.last_mut()
}
@ -259,7 +295,11 @@ impl<'a> WindowContext<'a> {
}
pub fn mouse_position(&self) -> Vector2F {
self.window.mouse_position
self.window.platform_window.mouse_position()
}
pub fn refreshing(&self) -> bool {
self.window.refreshing
}
pub fn text_layout_cache(&self) -> &TextLayoutCache {
@ -507,7 +547,9 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
self.dispatch_to_new_event_handlers(&event);
if !event_reused {
self.dispatch_event_2(&event);
}
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
@ -576,7 +618,7 @@ impl<'a> WindowContext<'a> {
// Synthesize one last drag event to end the drag
mouse_events.push(MouseEvent::Drag(MouseDrag {
region: Default::default(),
prev_mouse_position: self.window.mouse_position,
prev_mouse_position: self.window.last_mouse_position,
platform_event: MouseMovedEvent {
position: e.position,
pressed_button: Some(e.button),
@ -630,14 +672,14 @@ impl<'a> WindowContext<'a> {
if pressed_button.is_some() {
mouse_events.push(MouseEvent::Drag(MouseDrag {
region: Default::default(),
prev_mouse_position: self.window.mouse_position,
prev_mouse_position: self.window.last_mouse_position,
platform_event: e.clone(),
end: false,
}));
} else if let Some((_, clicked_button)) = self.window.clicked_region {
mouse_events.push(MouseEvent::Drag(MouseDrag {
region: Default::default(),
prev_mouse_position: self.window.mouse_position,
prev_mouse_position: self.window.last_mouse_position,
platform_event: e.clone(),
end: true,
}));
@ -697,7 +739,7 @@ impl<'a> WindowContext<'a> {
}
if let Some(position) = event.position() {
self.window.mouse_position = position;
self.window.last_mouse_position = position;
}
// 2. Dispatch mouse events on regions
@ -711,7 +753,7 @@ impl<'a> WindowContext<'a> {
match &mouse_event {
MouseEvent::Hover(_) => {
let mut highest_z_index = None;
let mouse_position = self.window.mouse_position.clone();
let mouse_position = self.mouse_position();
let window = &mut *self.window;
let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
for (region, z_index) in window.mouse_regions.iter().rev() {
@ -756,7 +798,7 @@ impl<'a> WindowContext<'a> {
MouseEvent::Down(_) | MouseEvent::Up(_) => {
for (region, _) in self.window.mouse_regions.iter().rev() {
if region.bounds.contains_point(self.window.mouse_position) {
if region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(region.clone());
if region.notify_on_click {
notified_views.insert(region.id().view_id());
@ -783,10 +825,7 @@ impl<'a> WindowContext<'a> {
// Find regions which still overlap with the mouse since the last MouseDown happened
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
if clicked_region_ids.contains(&mouse_region.id()) {
if mouse_region
.bounds
.contains_point(self.window.mouse_position)
{
if mouse_region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(mouse_region.clone());
} else {
// Let the view know that it hasn't been clicked anymore
@ -813,10 +852,7 @@ impl<'a> WindowContext<'a> {
| MouseEvent::ClickOut(_) => {
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
// NOT contains
if !mouse_region
.bounds
.contains_point(self.window.mouse_position)
{
if !mouse_region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(mouse_region.clone());
}
}
@ -825,10 +861,7 @@ impl<'a> WindowContext<'a> {
_ => {
for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
// Contains
if mouse_region
.bounds
.contains_point(self.window.mouse_position)
{
if mouse_region.bounds.contains_point(self.mouse_position()) {
valid_regions.push(mouse_region.clone());
}
}
@ -892,12 +925,24 @@ impl<'a> WindowContext<'a> {
any_event_handled
}
fn dispatch_to_new_event_handlers(&mut self, event: &Event) {
fn dispatch_event_2(&mut self, event: &Event) {
match event {
Event::MouseDown(event) => {
self.window.pressed_buttons.insert(event.button);
}
Event::MouseUp(event) => {
self.window.pressed_buttons.remove(&event.button);
}
_ => {}
}
if let Some(mouse_event) = event.mouse_event() {
let event_handlers = self.window.take_event_handlers();
for event_handler in event_handlers.iter().rev() {
if event_handler.event_type == mouse_event.type_id() {
(event_handler.handler)(mouse_event, self);
if !(event_handler.handler)(mouse_event, self) {
break;
}
}
}
self.window.event_handlers = event_handlers;
@ -1000,21 +1045,17 @@ impl<'a> WindowContext<'a> {
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
let mut new_parents = HashMap::default();
let mut views_to_notify_if_ancestors_change = HashMap::default();
rendered_root.layout(
SizeConstraint::new(window_size, window_size),
&mut new_parents,
&mut views_to_notify_if_ancestors_change,
refreshing,
self,
)?;
self.window.refreshing = refreshing;
rendered_root.layout(SizeConstraint::strict(window_size), self)?;
self.window.refreshing = false;
let views_to_notify_if_ancestors_change =
mem::take(&mut self.window.views_to_notify_if_ancestors_change);
for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
let mut current_view_id = view_id;
loop {
let old_parent_id = self.window.parents.get(&current_view_id);
let new_parent_id = new_parents.get(&current_view_id);
let new_parent_id = self.window.new_parents.get(&current_view_id);
if old_parent_id.is_none() && new_parent_id.is_none() {
break;
} else if old_parent_id == new_parent_id {
@ -1029,6 +1070,7 @@ impl<'a> WindowContext<'a> {
}
}
let new_parents = mem::take(&mut self.window.new_parents);
let old_parents = mem::replace(&mut self.window.parents, new_parents);
self.window
.rendered_views
@ -1043,9 +1085,7 @@ impl<'a> WindowContext<'a> {
let root_view_id = self.window.root_view().id();
let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
let mut scene_builder = SceneBuilder::new(scale_factor);
rendered_root.paint(
&mut scene_builder,
Vector2F::zero(),
RectF::from_points(Vector2F::zero(), window_size),
self,
@ -1055,7 +1095,7 @@ impl<'a> WindowContext<'a> {
.insert(root_view_id, rendered_root);
self.window.text_layout_cache.finish_frame();
let mut scene = scene_builder.build();
let mut scene = self.window.scene.build(scale_factor);
self.window.cursor_regions = scene.cursor_regions();
self.window.mouse_regions = scene.mouse_regions();
self.window.event_handlers = scene.take_event_handlers();
@ -1110,7 +1150,7 @@ impl<'a> WindowContext<'a> {
self.window.is_fullscreen
}
pub(crate) fn dispatch_action(&mut self, view_id: Option<usize>, action: &dyn Action) -> bool {
pub fn dispatch_action(&mut self, view_id: Option<usize>, action: &dyn Action) -> bool {
if let Some(view_id) = view_id {
self.halt_action_dispatch = false;
self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
@ -1203,6 +1243,10 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.bounds()
}
pub fn titlebar_height(&self) -> f32 {
self.window.titlebar_height
}
pub fn window_appearance(&self) -> Appearance {
self.window.appearance
}
@ -1274,6 +1318,43 @@ impl<'a> WindowContext<'a> {
};
handle
}
pub fn text_style(&self) -> TextStyle {
self.window
.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
pub fn push_text_style(&mut self, refinement: &TextStyleRefinement) -> Result<()> {
let mut style = self.text_style();
style.refine(refinement, self.font_cache())?;
self.window.text_style_stack.push(style);
Ok(())
}
pub fn pop_text_style(&mut self) {
self.window.text_style_stack.pop();
}
pub fn theme<T: 'static>(&self) -> &T {
self.window
.theme_stack
.iter()
.rev()
.find_map(|theme| theme.downcast_ref())
.ok_or_else(|| anyhow!("no theme provided of type {}", type_name::<T>()))
.unwrap()
}
pub fn push_theme<T: 'static>(&mut self, theme: T) {
self.window.theme_stack.push(Box::new(theme));
}
pub fn pop_theme(&mut self) {
self.window.theme_stack.pop();
}
}
#[derive(Default)]
@ -1289,9 +1370,12 @@ impl LayoutEngine {
where
C: IntoIterator<Item = LayoutId>,
{
Ok(self
.0
.new_with_children(style, &children.into_iter().collect::<Vec<_>>())?)
let children = children.into_iter().collect::<Vec<_>>();
if children.is_empty() {
Ok(self.0.new_leaf(style)?)
} else {
Ok(self.0.new_with_children(style, &children)?)
}
}
pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutId>
@ -1314,8 +1398,8 @@ impl LayoutEngine {
Ok(())
}
pub fn computed_layout(&mut self, node: LayoutId) -> Result<EngineLayout> {
Ok(self.0.layout(node)?.into())
pub fn computed_layout(&mut self, node: LayoutId) -> Result<Layout> {
Ok(Layout::from(self.0.layout(node)?))
}
}
@ -1339,7 +1423,7 @@ where
}
#[derive(Debug, Clone, Default)]
pub struct EngineLayout {
pub struct Layout {
pub bounds: RectF,
pub order: u32,
}
@ -1349,7 +1433,7 @@ pub struct MeasureParams {
pub available_space: Size<AvailableSpace>,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
Pixels(f32),
@ -1375,7 +1459,7 @@ impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
}
}
impl From<&taffy::tree::Layout> for EngineLayout {
impl From<&taffy::tree::Layout> for Layout {
fn from(value: &taffy::tree::Layout) -> Self {
Self {
bounds: RectF::new(
@ -1592,18 +1676,13 @@ impl<V: 'static> Element<V> for ChildView {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
cx.new_parents.insert(self.view_id, cx.view_id());
let parent_id = cx.view_id();
cx.window.new_parents.insert(self.view_id, parent_id);
let size = rendered_view
.layout(
constraint,
cx.new_parents,
cx.views_to_notify_if_ancestors_change,
cx.refreshing,
cx.view_context,
)
.layout(constraint, cx)
.log_err()
.unwrap_or(Vector2F::zero());
cx.window.rendered_views.insert(self.view_id, rendered_view);
@ -1620,16 +1699,15 @@ impl<V: 'static> Element<V> for ChildView {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
rendered_view
.paint(scene, bounds.origin(), visible_bounds, cx)
.paint(bounds.origin(), visible_bounds, cx)
.log_err();
cx.window.rendered_views.insert(self.view_id, rendered_view);
} else {

View File

@ -34,14 +34,12 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json, Action, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
ViewContext, WeakViewHandle, WindowContext,
json, Action, Entity, SizeConstraint, TypeTag, View, ViewContext, WeakViewHandle,
WindowContext,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
use core::panic;
use json::ToJson;
use smallvec::SmallVec;
use std::{
any::{type_name, Any},
borrow::Cow,
@ -61,17 +59,16 @@ pub trait Element<V: 'static>: 'static {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState);
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState;
fn rect_for_text_range(
@ -262,16 +259,15 @@ trait AnyElementState<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Vector2F;
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
);
fn rect_for_text_range(
@ -314,7 +310,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Vector2F {
let result;
*self = match mem::take(self) {
@ -348,11 +344,10 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
*self = match mem::take(self) {
ElementState::PostLayout {
@ -362,14 +357,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
mut layout,
} => {
let bounds = RectF::new(origin, size);
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
ElementState::PostPaint {
element,
constraint,
@ -387,14 +375,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
..
} => {
let bounds = RectF::new(origin, bounds.size());
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
ElementState::PostPaint {
element,
constraint,
@ -517,20 +498,19 @@ impl<V> AnyElement<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Vector2F {
self.state.layout(constraint, view, cx)
}
pub fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.state.paint(scene, origin, visible_bounds, view, cx);
self.state.paint(origin, visible_bounds, view, cx);
}
pub fn rect_for_text_range(
@ -578,7 +558,7 @@ impl<V: 'static> Element<V> for AnyElement<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.layout(constraint, view, cx);
(size, ())
@ -586,14 +566,13 @@ impl<V: 'static> Element<V> for AnyElement<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@ -646,17 +625,9 @@ impl<V> RootElement<V> {
}
pub trait AnyRootElement {
fn layout(
&mut self,
constraint: SizeConstraint,
new_parents: &mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
refreshing: bool,
cx: &mut WindowContext,
) -> Result<Vector2F>;
fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
cx: &mut WindowContext,
@ -671,32 +642,16 @@ pub trait AnyRootElement {
}
impl<V: View> AnyRootElement for RootElement<V> {
fn layout(
&mut self,
constraint: SizeConstraint,
new_parents: &mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
refreshing: bool,
cx: &mut WindowContext,
) -> Result<Vector2F> {
fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
let view = self
.view
.upgrade(cx)
.ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
view.update(cx, |view, cx| {
let mut cx = LayoutContext::new(
cx,
new_parents,
views_to_notify_if_ancestors_change,
refreshing,
);
Ok(self.element.layout(constraint, view, &mut cx))
})
view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
cx: &mut WindowContext,
@ -707,9 +662,7 @@ impl<V: View> AnyRootElement for RootElement<V> {
.ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
view.update(cx, |view, cx| {
let mut cx = PaintContext::new(cx);
self.element
.paint(scene, origin, visible_bounds, view, &mut cx);
self.element.paint(origin, visible_bounds, view, cx);
Ok(())
})
}

View File

@ -1,7 +1,6 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
json, AnyElement, Element, SizeConstraint, ViewContext,
};
use json::ToJson;
@ -49,7 +48,7 @@ impl<V: 'static> Element<V> for Align<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.max;
constraint.min = Vector2F::zero();
@ -65,12 +64,11 @@ impl<V: 'static> Element<V> for Align<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment;
@ -79,7 +77,6 @@ impl<V: 'static> Element<V> for Align<V> {
let child_target = child_center + child_center * self.alignment;
self.child.paint(
scene,
bounds.origin() - (child_target - my_target),
visible_bounds,
view,

View File

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element;
use crate::{
json::{self, json},
PaintContext, SceneBuilder, ViewContext,
ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@ -15,7 +15,7 @@ pub struct Canvas<V, F>(F, PhantomData<V>);
impl<V, F> Canvas<V, F>
where
F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
F: FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
{
pub fn new(f: F) -> Self {
Self(f, PhantomData)
@ -24,7 +24,7 @@ where
impl<V: 'static, F> Element<V> for Canvas<V, F>
where
F: 'static + FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
{
type LayoutState = ();
type PaintState = ();
@ -33,7 +33,7 @@ where
&mut self,
constraint: crate::SizeConstraint,
_: &mut V,
_: &mut crate::LayoutContext<V>,
_: &mut crate::ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() {
constraint.max.x()
@ -50,14 +50,13 @@ where
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.0(scene, bounds, visible_bounds, view, cx)
self.0(bounds, visible_bounds, view, cx)
}
fn rect_for_text_range(

View File

@ -3,10 +3,7 @@ use std::ops::Range;
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
use crate::{
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
};
use crate::{json, AnyElement, Element, SizeConstraint, ViewContext};
pub struct Clipped<V> {
child: AnyElement<V>,
@ -26,24 +23,23 @@ impl<V: 'static> Element<V> for Clipped<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(bounds), |scene| {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
})
cx.scene().push_layer(Some(bounds));
let state = self.child.paint(bounds.origin(), visible_bounds, view, cx);
cx.scene().pop_layer();
state
}
fn rect_for_text_range(

View File

@ -2,9 +2,7 @@ use std::{any::Any, marker::PhantomData};
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use crate::{
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
};
use crate::{AnyElement, Element, SizeConstraint, ViewContext};
use super::Empty;
@ -284,14 +282,14 @@ impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.element.is_none() {
let element = self
.component
.take()
.expect("Component can only be rendered once")
.render(view, cx.view_context());
.render(view, cx);
self.element = Some(element);
}
let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
@ -300,17 +298,16 @@ impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.element
.as_mut()
.expect("Layout should always be called before paint")
.paint(scene, bounds.origin(), visible_bounds, view, cx)
.paint(bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(

View File

@ -5,8 +5,7 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
json, AnyElement, Element, SizeConstraint, ViewContext,
};
pub struct ConstrainedBox<V> {
@ -16,7 +15,7 @@ pub struct ConstrainedBox<V> {
pub enum Constraint<V> {
Static(SizeConstraint),
Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
}
impl<V> ToJson for Constraint<V> {
@ -38,8 +37,7 @@ impl<V: 'static> ConstrainedBox<V> {
pub fn dynamically(
mut self,
constraint: impl 'static
+ FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
) -> Self {
self.constraint = Constraint::Dynamic(Box::new(constraint));
self
@ -121,7 +119,7 @@ impl<V: 'static> ConstrainedBox<V> {
&mut self,
input_constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> SizeConstraint {
match &mut self.constraint {
Constraint::Static(constraint) => *constraint,
@ -140,7 +138,7 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
&mut self,
mut parent_constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = self.constraint(parent_constraint, view, cx);
parent_constraint.min = parent_constraint.min.max(constraint.min);
@ -152,17 +150,15 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(visible_bounds), |scene| {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
})
cx.scene().push_layer(Some(visible_bounds));
self.child.paint(bounds.origin(), visible_bounds, view, cx);
cx.scene().pop_layer();
}
fn rect_for_text_range(

View File

@ -9,8 +9,8 @@ use crate::{
},
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CornerRadii, CursorRegion, Quad},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
scene::{self, CornerRadii, CursorRegion, Quad},
AnyElement, Element, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -206,6 +206,163 @@ impl<V> Container<V> {
}
}
#[derive(Copy, Clone, Debug, Default, JsonSchema)]
pub struct Border {
pub color: Color,
pub width: f32,
pub overlay: bool,
pub top: bool,
pub bottom: bool,
pub left: bool,
pub right: bool,
}
impl Into<scene::Border> for Border {
fn into(self) -> scene::Border {
scene::Border {
color: self.color,
left: if self.left { self.width } else { 0.0 },
right: if self.right { self.width } else { 0.0 },
top: if self.top { self.width } else { 0.0 },
bottom: if self.bottom { self.width } else { 0.0 },
}
}
}
impl Border {
pub fn new(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: false,
left: false,
bottom: false,
right: false,
}
}
pub fn all(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: true,
left: true,
bottom: true,
right: true,
}
}
pub fn top(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.top = true;
border
}
pub fn left(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.left = true;
border
}
pub fn bottom(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.bottom = true;
border
}
pub fn right(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.right = true;
border
}
pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
self.top = top;
self.left = left;
self.bottom = bottom;
self.right = right;
self
}
pub fn top_width(&self) -> f32 {
if self.top {
self.width
} else {
0.0
}
}
pub fn left_width(&self) -> f32 {
if self.left {
self.width
} else {
0.0
}
}
}
impl<'de> Deserialize<'de> for Border {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct BorderData {
pub width: f32,
pub color: Color,
#[serde(default)]
pub overlay: bool,
#[serde(default)]
pub top: bool,
#[serde(default)]
pub right: bool,
#[serde(default)]
pub bottom: bool,
#[serde(default)]
pub left: bool,
}
let data = BorderData::deserialize(deserializer)?;
let mut border = Border {
width: data.width,
color: data.color,
overlay: data.overlay,
top: data.top,
bottom: data.bottom,
left: data.left,
right: data.right,
};
if !border.top && !border.bottom && !border.left && !border.right {
border.top = true;
border.bottom = true;
border.left = true;
border.right = true;
}
Ok(border)
}
}
impl ToJson for Border {
fn to_json(&self) -> serde_json::Value {
let mut value = json!({});
if self.top {
value["top"] = json!(self.width);
}
if self.right {
value["right"] = json!(self.width);
}
if self.bottom {
value["bottom"] = json!(self.width);
}
if self.left {
value["left"] = json!(self.width);
}
value
}
}
impl<V: 'static> Element<V> for Container<V> {
type LayoutState = ();
type PaintState = ();
@ -214,7 +371,7 @@ impl<V: 'static> Element<V> for Container<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size_buffer = self.margin_size() + self.padding_size();
if !self.style.border.overlay {
@ -230,12 +387,11 @@ impl<V: 'static> Element<V> for Container<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@ -243,7 +399,7 @@ impl<V: 'static> Element<V> for Container<V> {
);
if let Some(shadow) = self.style.shadow.as_ref() {
scene.push_shadow(scene::Shadow {
cx.scene().push_shadow(scene::Shadow {
bounds: quad_bounds + shadow.offset,
corner_radii: self.style.corner_radii,
sigma: shadow.blur,
@ -253,7 +409,7 @@ impl<V: 'static> Element<V> for Container<V> {
if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
if let Some(style) = self.style.cursor {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: hit_bounds,
style,
});
@ -264,29 +420,28 @@ impl<V: 'static> Element<V> for Container<V> {
quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
if self.style.border.overlay {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.background_color,
border: Default::default(),
corner_radii: self.style.corner_radii.into(),
});
self.child
.paint(scene, child_origin, visible_bounds, view, cx);
self.child.paint(child_origin, visible_bounds, view, cx);
scene.push_layer(None);
scene.push_quad(Quad {
cx.scene().push_layer(None);
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: self.style.border,
border: self.style.border.into(),
corner_radii: self.style.corner_radii.into(),
});
scene.pop_layer();
cx.scene().pop_layer();
} else {
scene.push_quad(Quad {
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.background_color,
border: self.style.border,
border: self.style.border.into(),
corner_radii: self.style.corner_radii.into(),
});
@ -295,18 +450,17 @@ impl<V: 'static> Element<V> for Container<V> {
self.style.border.left_width(),
self.style.border.top_width(),
);
self.child
.paint(scene, child_origin, visible_bounds, view, cx);
self.child.paint(child_origin, visible_bounds, view, cx);
if self.style.overlay_color.is_some() {
scene.push_layer(None);
scene.push_quad(Quad {
cx.scene().push_layer(None);
cx.scene().push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: Default::default(),
corner_radii: self.style.corner_radii.into(),
});
scene.pop_layer();
cx.scene().pop_layer();
}
}
}

View File

@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
LayoutContext, PaintContext, SceneBuilder, ViewContext,
ViewContext,
};
use crate::{Element, SizeConstraint};
@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Empty {
&mut self,
constraint: SizeConstraint,
_: &mut V,
_: &mut LayoutContext<V>,
_: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let x = if constraint.max.x().is_finite() && !self.collapsed {
constraint.max.x()
@ -52,12 +52,11 @@ impl<V: 'static> Element<V> for Empty {
fn paint(
&mut self,
_: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
_: &mut ViewContext<V>,
) -> Self::PaintState {
}

View File

@ -2,8 +2,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
json, AnyElement, Element, SizeConstraint, ViewContext,
};
use serde_json::json;
@ -43,7 +42,7 @@ impl<V: 'static> Element<V> for Expanded<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.full_width {
constraint.min.set_x(constraint.max.x());
@ -57,15 +56,13 @@ impl<V: 'static> Element<V> for Expanded<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View File

@ -2,8 +2,7 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
SizeConstraint, Vector2FExt, ViewContext,
AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@ -85,7 +84,7 @@ impl<V: 'static> Flex<V> {
remaining_flex: &mut f32,
cross_axis_max: &mut f32,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) {
let cross_axis = self.axis.invert();
for child in self.children.iter_mut() {
@ -136,7 +135,7 @@ impl<V: 'static> Element<V> for Flex<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut total_flex = None;
let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing;
@ -225,7 +224,7 @@ impl<V: 'static> Element<V> for Flex<V> {
}
if let Some(scroll_state) = self.scroll_state.as_ref() {
scroll_state.0.update(cx.view_context(), |scroll_state, _| {
scroll_state.0.update(cx, |scroll_state, _| {
if let Some(scroll_to) = scroll_state.scroll_to.take() {
let visible_start = scroll_state.scroll_position.get();
let visible_end = visible_start + size.along(self.axis);
@ -260,26 +259,25 @@ impl<V: 'static> Element<V> for Flex<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
let mut remaining_space = *remaining_space;
let overflowing = remaining_space < 0.;
if overflowing {
scene.push_layer(Some(visible_bounds));
cx.scene().push_layer(Some(visible_bounds));
}
if let Some(scroll_state) = &self.scroll_state {
scene.push_mouse_region(
crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
if let Some((scroll_state, id)) = &self.scroll_state {
let scroll_state = scroll_state.read(cx).clone();
cx.scene().push_mouse_region(
crate::MouseRegion::new::<Self>(*id, 0, bounds)
.on_scroll({
let scroll_state = scroll_state.0.read(cx).clone();
let axis = self.axis;
move |e, _: &mut V, cx| {
if remaining_space < 0. {
@ -358,7 +356,7 @@ impl<V: 'static> Element<V> for Flex<V> {
aligned_child_origin
};
child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
child.paint(aligned_child_origin, visible_bounds, view, cx);
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x() + self.spacing, 0.0),
@ -367,7 +365,7 @@ impl<V: 'static> Element<V> for Flex<V> {
}
if overflowing {
scene.pop_layer();
cx.scene().pop_layer();
}
}
@ -443,7 +441,7 @@ impl<V: 'static> Element<V> for FlexItem<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
(size, ())
@ -451,15 +449,13 @@ impl<V: 'static> Element<V> for FlexItem<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
self.child.paint(bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(

View File

@ -3,7 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
AnyElement, Element, SizeConstraint, ViewContext,
};
pub struct Hook<V> {
@ -36,7 +36,7 @@ impl<V: 'static> Element<V> for Hook<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
if let Some(handler) = self.after_layout.as_mut() {
@ -47,15 +47,13 @@ impl<V: 'static> Element<V> for Hook<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View File

@ -1,12 +1,11 @@
use super::constrain_size_preserving_aspect_ratio;
use super::{constrain_size_preserving_aspect_ratio, Border};
use crate::{
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
scene, Element, ImageData, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -65,7 +64,7 @@ impl<V: 'static> Element<V> for Image {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let data = match &self.source {
ImageSource::Path(path) => match cx.asset_cache.png(path) {
@ -92,17 +91,16 @@ impl<V: 'static> Element<V> for Image {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
if let Some(data) = layout {
scene.push_image(scene::Image {
cx.scene().push_image(scene::Image {
bounds,
border: self.style.border,
border: self.style.border.into(),
corner_radii: self.style.corner_radius.into(),
grayscale: self.style.grayscale,
data: data.clone(),

View File

@ -39,7 +39,7 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, AnyElement<V>) {
let mut element = if let Some(keystrokes) =
cx.keystrokes_for_action(self.view_id, self.action.as_ref())
@ -61,14 +61,13 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
element: &mut AnyElement<V>,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
element.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View File

@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
Element, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -136,7 +136,7 @@ impl<V: 'static> Element<V> for Label {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let runs = self.compute_runs();
let line = cx.text_layout_cache().layout_str(
@ -158,21 +158,14 @@ impl<V: 'static> Element<V> for Label {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
line: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
line.paint(
scene,
bounds.origin(),
visible_bounds,
bounds.size().y(),
cx,
)
line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
}
fn rect_for_text_range(

View File

@ -4,8 +4,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
ViewContext,
AnyElement, Element, MouseRegion, SizeConstraint, ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
@ -100,7 +99,7 @@ impl<V: 'static> Element<V> for List<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let state = &mut *self.state.0.borrow_mut();
let size = constraint.max;
@ -108,7 +107,7 @@ impl<V: 'static> Element<V> for List<V> {
item_constraint.min.set_y(0.);
item_constraint.max.set_y(f32::INFINITY);
if cx.refreshing || state.last_layout_width != Some(size.x()) {
if cx.refreshing() || state.last_layout_width != Some(size.x()) {
state.rendered_range = 0..0;
state.items = SumTree::from_iter(
(0..state.items.summary().count).map(|_| ListItem::Unrendered),
@ -250,17 +249,17 @@ impl<V: 'static> Element<V> for List<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
scroll_top: &mut ListOffset,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
scene.push_mouse_region(
MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
cx.scene().push_layer(Some(visible_bounds));
let view_id = cx.view_id();
cx.scene()
.push_mouse_region(MouseRegion::new::<Self>(view_id, 0, bounds).on_scroll({
let state = self.state.clone();
let height = bounds.height();
let scroll_top = scroll_top.clone();
@ -274,17 +273,14 @@ impl<V: 'static> Element<V> for List<V> {
cx,
)
}
}),
);
}));
let state = &mut *self.state.0.borrow_mut();
for (element, origin) in state.visible_elements(bounds, scroll_top) {
element
.borrow_mut()
.paint(scene, origin, visible_bounds, view, cx);
element.borrow_mut().paint(origin, visible_bounds, view, cx);
}
scene.pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(
@ -453,7 +449,7 @@ impl<V: 'static> StateInner<V> {
existing_element: Option<&ListItem<V>>,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> Option<Rc<RefCell<AnyElement<V>>>> {
if let Some(ListItem::Rendered(element)) = existing_element {
Some(element.clone())
@ -647,7 +643,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use crate::{elements::Empty, geometry::vector::vec2f, Entity};
use rand::prelude::*;
use std::env;
@ -666,15 +662,7 @@ mod tests {
});
let mut list = List::new(state.clone());
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
let (size, _) = list.layout(constraint, &mut view, cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
@ -698,13 +686,7 @@ mod tests {
cx,
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
assert_eq!(
logical_scroll_top,
ListOffset {
@ -728,13 +710,7 @@ mod tests {
}
);
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
assert_eq!(size, vec2f(100., 40.));
assert_eq!(
state.0.borrow().items.summary().clone(),
@ -852,18 +828,10 @@ mod tests {
let mut list = List::new(state.clone());
let window_size = vec2f(width, height);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (size, logical_scroll_top) = list.layout(
SizeConstraint::new(vec2f(0., 0.), window_size),
&mut view,
&mut layout_cx,
cx,
);
assert_eq!(size, window_size);
last_logical_scroll_top = Some(logical_scroll_top);
@ -976,20 +944,12 @@ mod tests {
&mut self,
_: SizeConstraint,
_: &mut V,
_: &mut LayoutContext<V>,
_: &mut ViewContext<V>,
) -> (Vector2F, ()) {
(self.size, ())
}
fn paint(
&mut self,
_: &mut SceneBuilder,
_: RectF,
_: RectF,
_: &mut (),
_: &mut V,
_: &mut PaintContext<V>,
) {
fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext<V>) {
unimplemented!()
}

View File

@ -10,8 +10,8 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
SceneBuilder, SizeConstraint, TypeTag, ViewContext,
AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag,
ViewContext,
};
use serde_json::json;
use std::ops::Range;
@ -236,26 +236,21 @@ impl<V: 'static> MouseEventHandler<V> {
.round_out()
}
fn paint_regions(
&self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
cx: &mut ViewContext<V>,
) {
fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut ViewContext<V>) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
let hit_bounds = self.hit_bounds(visible_bounds);
if let Some(style) = self.cursor_style {
scene.push_cursor_region(CursorRegion {
cx.scene().push_cursor_region(CursorRegion {
bounds: hit_bounds,
style,
});
}
scene.push_mouse_region(
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::from_handlers(
self.tag,
cx.view_id(),
view_id,
self.region_id,
hit_bounds,
self.handlers.clone(),
@ -275,31 +270,27 @@ impl<V: 'static> Element<V> for MouseEventHandler<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
if self.above {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
scene.paint_layer(None, |scene| {
self.paint_regions(scene, bounds, visible_bounds, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
cx.paint_layer(None, |cx| {
self.paint_regions(bounds, visible_bounds, cx);
});
} else {
self.paint_regions(scene, bounds, visible_bounds, cx);
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.paint_regions(bounds, visible_bounds, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
}

View File

@ -3,8 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, ViewContext,
AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext,
};
use serde_json::json;
@ -125,7 +124,7 @@ impl<V: 'static> Element<V> for Overlay<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let constraint = if self.anchor_position.is_some() {
SizeConstraint::new(Vector2F::zero(), cx.window_size())
@ -138,12 +137,11 @@ impl<V: 'static> Element<V> for Overlay<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_: RectF,
size: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => {
@ -213,25 +211,23 @@ impl<V: 'static> Element<V> for Overlay<V> {
OverlayFitMode::None => {}
}
scene.paint_stacking_context(None, self.z_index, |scene| {
if self.hoverable {
enum OverlayHoverCapture {}
// Block hovers in lower stacking contexts
scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
cx.view_id(),
cx.view_id(),
bounds,
cx.scene().push_stacking_context(None, self.z_index);
if self.hoverable {
enum OverlayHoverCapture {}
// Block hovers in lower stacking contexts
let view_id = cx.view_id();
cx.scene()
.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
view_id, view_id, bounds,
));
}
self.child.paint(
scene,
bounds.origin(),
RectF::new(Vector2F::zero(), cx.window_size()),
view,
cx,
);
});
}
self.child.paint(
bounds.origin(),
RectF::new(Vector2F::zero(), cx.window_size()),
view,
cx,
);
cx.scene().pop_stacking_context();
}
fn rect_for_text_range(

View File

@ -7,8 +7,7 @@ use serde_json::json;
use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, TypeTag, View, ViewContext,
AnyElement, AppContext, Axis, Element, MouseRegion, SizeConstraint, TypeTag, View, ViewContext,
};
#[derive(Copy, Clone, Debug)]
@ -105,77 +104,77 @@ impl<V: 'static> Element<V> for Resizable<V> {
&mut self,
constraint: crate::SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), constraint)
}
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF,
constraint: &mut SizeConstraint,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.push_stacking_context(None, None);
cx.scene().push_stacking_context(None, None);
let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
enum ResizeHandle {}
scene.push_mouse_region(
MouseRegion::new::<ResizeHandle>(
cx.view_id(),
self.handle_side as usize,
handle_region,
)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_click(MouseButton::Left, {
let on_resize = self.on_resize.clone();
move |click, v, cx| {
if click.click_count == 2 {
on_resize.borrow_mut()(v, None, cx);
let view_id = cx.view_id();
cx.scene().push_mouse_region(
MouseRegion::new::<ResizeHandle>(view_id, self.handle_side as usize, handle_region)
.on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_click(MouseButton::Left, {
let on_resize = self.on_resize.clone();
move |click, v, cx| {
if click.click_count == 2 {
on_resize.borrow_mut()(v, None, cx);
}
}
}
})
.on_drag(MouseButton::Left, {
let bounds = bounds.clone();
let side = self.handle_side;
let prev_size = side.relevant_component(bounds.size());
let min_size = side.relevant_component(constraint.min);
let max_size = side.relevant_component(constraint.max);
let on_resize = self.on_resize.clone();
let tag = self.tag;
move |event, view: &mut V, cx| {
if event.end {
return;
})
.on_drag(MouseButton::Left, {
let bounds = bounds.clone();
let side = self.handle_side;
let prev_size = side.relevant_component(bounds.size());
let min_size = side.relevant_component(constraint.min);
let max_size = side.relevant_component(constraint.max);
let on_resize = self.on_resize.clone();
let tag = self.tag;
move |event, view: &mut V, cx| {
if event.end {
return;
}
let Some((bounds, _)) = get_bounds(tag, cx) else {
return;
};
let new_size_raw = match side {
// Handle on top side of element => Element is on bottom
HandleSide::Top => {
bounds.height() + bounds.origin_y() - event.position.y()
}
// Handle on right side of element => Element is on left
HandleSide::Right => event.position.x() - bounds.lower_left().x(),
// Handle on left side of element => Element is on the right
HandleSide::Left => {
bounds.width() + bounds.origin_x() - event.position.x()
}
// Handle on bottom side of element => Element is on the top
HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
};
let new_size = min_size.max(new_size_raw).min(max_size).round();
if new_size != prev_size {
on_resize.borrow_mut()(view, Some(new_size), cx);
}
}
let Some((bounds, _)) = get_bounds(tag, cx) else {
return;
};
let new_size_raw = match side {
// Handle on top side of element => Element is on bottom
HandleSide::Top => bounds.height() + bounds.origin_y() - event.position.y(),
// Handle on right side of element => Element is on left
HandleSide::Right => event.position.x() - bounds.lower_left().x(),
// Handle on left side of element => Element is on the right
HandleSide::Left => bounds.width() + bounds.origin_x() - event.position.x(),
// Handle on bottom side of element => Element is on the top
HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
};
let new_size = min_size.max(new_size_raw).min(max_size).round();
if new_size != prev_size {
on_resize.borrow_mut()(view, Some(new_size), cx);
}
}
}),
}),
);
scene.push_cursor_region(crate::CursorRegion {
cx.scene().push_cursor_region(crate::CursorRegion {
bounds: handle_region,
style: match self.handle_side.axis() {
Axis::Horizontal => CursorStyle::ResizeLeftRight,
@ -183,10 +182,9 @@ impl<V: 'static> Element<V> for Resizable<V> {
},
});
scene.pop_stacking_context();
cx.scene().pop_stacking_context();
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(
@ -242,26 +240,24 @@ impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
&mut self,
constraint: crate::SizeConstraint,
view: &mut V,
cx: &mut crate::LayoutContext<V>,
cx: &mut crate::ViewContext<V>,
) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
(self.child.layout(constraint, view, cx), ())
}
fn paint(
&mut self,
scene: &mut crate::SceneBuilder,
bounds: pathfinder_geometry::rect::RectF,
visible_bounds: pathfinder_geometry::rect::RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut crate::PaintContext<V>,
cx: &mut crate::ViewContext<V>,
) -> Self::PaintState {
cx.update_default_global::<ProviderMap, _, _>(|map, _| {
map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
});
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)
self.child.paint(bounds.origin(), visible_bounds, view, cx)
}
fn rect_for_text_range(

View File

@ -3,7 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
AnyElement, Element, SizeConstraint, ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Stack<V> {
&mut self,
mut constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let mut size = constraint.min;
let mut children = self.children.iter_mut();
@ -52,17 +52,16 @@ impl<V: 'static> Element<V> for Stack<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
for child in &mut self.children {
scene.paint_layer(None, |scene| {
child.paint(scene, bounds.origin(), visible_bounds, view, cx);
});
cx.scene().push_layer(None);
child.paint(bounds.origin(), visible_bounds, view, cx);
cx.scene().pop_layer();
}
}

View File

@ -1,13 +1,12 @@
use super::constrain_size_preserving_aspect_ratio;
use crate::json::ToJson;
use crate::PaintContext;
use crate::{
color::Color,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
scene, Element, LayoutContext, SceneBuilder, SizeConstraint, ViewContext,
scene, Element, SizeConstraint, ViewContext,
};
use schemars::JsonSchema;
use serde_derive::Deserialize;
@ -49,7 +48,7 @@ impl<V: 'static> Element<V> for Svg {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
match cx.asset_cache.svg(&self.path) {
Ok(tree) => {
@ -69,15 +68,14 @@ impl<V: 'static> Element<V> for Svg {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
if let Some(svg) = svg.clone() {
scene.push_icon(scene::Icon {
cx.scene().push_icon(scene::Icon {
bounds,
svg,
path: self.path.clone(),

View File

@ -7,8 +7,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
TextLayoutCache, ViewContext,
Element, FontCache, SizeConstraint, TextLayoutCache, ViewContext, WindowContext,
};
use log::warn;
use serde_json::json;
@ -21,7 +20,7 @@ pub struct Text {
highlights: Option<Box<[(Range<usize>, HighlightStyle)]>>,
custom_runs: Option<(
Box<[Range<usize>]>,
Box<dyn FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext)>,
Box<dyn FnMut(usize, RectF, &mut WindowContext)>,
)>,
}
@ -58,7 +57,7 @@ impl Text {
pub fn with_custom_runs(
mut self,
runs: impl Into<Box<[Range<usize>]>>,
callback: impl 'static + FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext),
callback: impl 'static + FnMut(usize, RectF, &mut WindowContext),
) -> Self {
self.custom_runs = Some((runs.into(), Box::new(callback)));
self
@ -78,7 +77,7 @@ impl<V: 'static> Element<V> for Text {
&mut self,
constraint: SizeConstraint,
_: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
// Convert the string and highlight ranges into an iterator of highlighted chunks.
@ -166,16 +165,15 @@ impl<V: 'static> Element<V> for Text {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();
let mut callback = |_, _, _: &mut SceneBuilder, _: &mut AppContext| {};
let mut callback = |_, _, _: &mut WindowContext| {};
let mouse_runs;
let custom_run_callback;
@ -202,7 +200,6 @@ impl<V: 'static> Element<V> for Text {
if boundaries.intersects(visible_bounds) {
if self.soft_wrap {
line.paint_wrapped(
scene,
origin,
visible_bounds,
layout.line_height,
@ -210,7 +207,7 @@ impl<V: 'static> Element<V> for Text {
cx,
);
} else {
line.paint(scene, origin, visible_bounds, layout.line_height, cx);
line.paint(origin, visible_bounds, layout.line_height, cx);
}
}
@ -248,7 +245,7 @@ impl<V: 'static> Element<V> for Text {
*run_origin,
glyph_origin + vec2f(0., layout.line_height),
);
custom_run_callback(*run_ix, bounds, scene, cx);
custom_run_callback(*run_ix, bounds, cx);
*run_origin =
vec2f(origin.x(), glyph_origin.y() + layout.line_height);
}
@ -264,7 +261,7 @@ impl<V: 'static> Element<V> for Text {
run_origin,
glyph_origin + vec2f(0., layout.line_height),
);
custom_run_callback(run_ix, bounds, scene, cx);
custom_run_callback(run_ix, bounds, cx);
custom_runs.next();
}
@ -294,7 +291,7 @@ impl<V: 'static> Element<V> for Text {
run_origin,
line_end + vec2f(0., layout.line_height),
);
custom_run_callback(run_ix, bounds, scene, cx);
custom_run_callback(run_ix, bounds, cx);
if end_offset == run_end_offset {
custom_runs.next();
}
@ -411,18 +408,10 @@ mod tests {
let mut view = TestView;
fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
let mut new_parents = Default::default();
let mut notify_views_if_parents_change = Default::default();
let mut layout_cx = LayoutContext::new(
cx,
&mut new_parents,
&mut notify_views_if_parents_change,
false,
);
let (_, state) = text.layout(
SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
&mut view,
&mut layout_cx,
cx,
);
assert_eq!(state.shaped_lines.len(), 2);
assert_eq!(state.wrap_boundaries.len(), 2);

View File

@ -6,8 +6,7 @@ use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
Task, TypeTag, ViewContext,
Action, Axis, ElementStateHandle, SizeConstraint, Task, TypeTag, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@ -189,7 +188,7 @@ impl<V: 'static> Element<V> for Tooltip<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
let size = self.child.layout(constraint, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
@ -204,17 +203,15 @@ impl<V: 'static> Element<V> for Tooltip<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);
self.child.paint(bounds.origin(), visible_bounds, view, cx);
if let Some(tooltip) = self.tooltip.as_mut() {
tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
tooltip.paint(bounds.origin(), visible_bounds, view, cx);
}
}

View File

@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, ViewContext,
AnyElement, MouseRegion, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@ -158,7 +158,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
&mut self,
constraint: SizeConstraint,
view: &mut V,
cx: &mut LayoutContext<V>,
cx: &mut ViewContext<V>,
) -> (Vector2F, Self::LayoutState) {
if constraint.max.y().is_infinite() {
unimplemented!(
@ -272,18 +272,17 @@ impl<V: 'static> Element<V> for UniformList<V> {
fn paint(
&mut self,
scene: &mut SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
cx.scene().push_layer(Some(visible_bounds));
scene.push_mouse_region(
cx.scene().push_mouse_region(
MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
let scroll_max = layout.scroll_max;
let state = self.state.clone();
@ -312,11 +311,11 @@ impl<V: 'static> Element<V> for UniformList<V> {
);
for item in &mut layout.items {
item.paint(scene, item_origin, visible_bounds, view, cx);
item.paint(item_origin, visible_bounds, view, cx);
item_origin += vec2f(0.0, layout.item_height);
}
scene.pop_layer();
cx.scene().pop_layer();
}
fn rect_for_text_range(

View File

@ -60,7 +60,7 @@ pub struct Features {
pub zero: Option<bool>,
}
#[derive(Clone, Debug, JsonSchema, Refineable)]
#[derive(Clone, Debug, JsonSchema)]
pub struct TextStyle {
pub color: Color,
pub font_family_name: Arc<str>,
@ -80,19 +80,78 @@ impl TextStyle {
..Default::default()
}
}
}
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
TextStyle {
color: refinement.color.unwrap_or(self.color),
font_family_name: refinement
.font_family_name
.unwrap_or_else(|| self.font_family_name.clone()),
font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
font_id: refinement.font_id.unwrap_or(self.font_id),
font_size: refinement.font_size.unwrap_or(self.font_size),
font_properties: refinement.font_properties.unwrap_or(self.font_properties),
underline: refinement.underline.unwrap_or(self.underline),
soft_wrap: refinement.soft_wrap.unwrap_or(self.soft_wrap),
impl TextStyle {
pub fn refine(
&mut self,
refinement: &TextStyleRefinement,
font_cache: &FontCache,
) -> Result<()> {
if let Some(font_size) = refinement.font_size {
self.font_size = font_size;
}
if let Some(color) = refinement.color {
self.color = color;
}
if let Some(underline) = refinement.underline {
self.underline = underline;
}
let mut update_font_id = false;
if let Some(font_family) = refinement.font_family.clone() {
self.font_family_id = font_cache.load_family(&[&font_family], &Default::default())?;
self.font_family_name = font_family;
update_font_id = true;
}
if let Some(font_weight) = refinement.font_weight {
self.font_properties.weight = font_weight;
update_font_id = true;
}
if let Some(font_style) = refinement.font_style {
self.font_properties.style = font_style;
update_font_id = true;
}
if update_font_id {
self.font_id = font_cache.select_font(self.font_family_id, &self.font_properties)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub struct TextStyleRefinement {
pub color: Option<Color>,
pub font_family: Option<Arc<str>>,
pub font_size: Option<f32>,
pub font_weight: Option<Weight>,
pub font_style: Option<Style>,
pub underline: Option<Underline>,
}
impl Refineable for TextStyleRefinement {
type Refinement = Self;
fn refine(&mut self, refinement: &Self::Refinement) {
if refinement.color.is_some() {
self.color = refinement.color;
}
if refinement.font_family.is_some() {
self.font_family = refinement.font_family.clone();
}
if refinement.font_size.is_some() {
self.font_size = refinement.font_size;
}
if refinement.font_weight.is_some() {
self.font_weight = refinement.font_weight;
}
if refinement.font_style.is_some() {
self.font_style = refinement.font_style;
}
if refinement.underline.is_some() {
self.underline = refinement.underline;
}
}
}

View File

@ -1,3 +1,5 @@
use std::fmt::Debug;
use super::scene::{Path, PathVertex};
use crate::{color::Color, json::ToJson};
pub use pathfinder_geometry::*;
@ -133,13 +135,14 @@ impl ToJson for RectF {
}
}
#[derive(Refineable)]
pub struct Point<T: Clone + Default> {
#[derive(Refineable, Debug)]
#[refineable(debug)]
pub struct Point<T: Clone + Default + Debug> {
pub x: T,
pub y: T,
}
impl<T: Clone + Default> Clone for Point<T> {
impl<T: Clone + Default + Debug> Clone for Point<T> {
fn clone(&self) -> Self {
Self {
x: self.x.clone(),
@ -148,7 +151,7 @@ impl<T: Clone + Default> Clone for Point<T> {
}
}
impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
fn into(self) -> taffy::geometry::Point<T> {
taffy::geometry::Point {
x: self.x,
@ -157,13 +160,14 @@ impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
}
}
#[derive(Clone, Refineable)]
pub struct Size<T: Clone + Default> {
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct Size<T: Clone + Default + Debug> {
pub width: T,
pub height: T,
}
impl<S, T: Clone + Default> From<taffy::geometry::Size<S>> for Size<T>
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
where
S: Into<T>,
{
@ -175,7 +179,7 @@ where
}
}
impl<S, T: Clone + Default> Into<taffy::geometry::Size<S>> for Size<T>
impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
where
T: Into<S>,
{
@ -222,34 +226,15 @@ impl Size<Length> {
}
}
#[derive(Clone, Default, Refineable)]
pub struct Edges<T: Clone + Default> {
#[derive(Clone, Default, Refineable, Debug)]
#[refineable(debug)]
pub struct Edges<T: Clone + Default + Debug> {
pub top: T,
pub right: T,
pub bottom: T,
pub left: T,
}
impl Edges<DefiniteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
bottom: self.bottom.to_taffy(rem_size),
left: self.left.to_taffy(rem_size),
}
}
}
impl Edges<Length> {
pub fn auto() -> Self {
Self {
@ -282,12 +267,76 @@ impl Edges<Length> {
}
}
impl Edges<DefiniteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
bottom: self.bottom.to_taffy(rem_size),
left: self.left.to_taffy(rem_size),
}
}
}
impl Edges<AbsoluteLength> {
pub fn zero() -> Self {
Self {
top: pixels(0.),
right: pixels(0.),
bottom: pixels(0.),
left: pixels(0.),
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
taffy::geometry::Rect {
top: self.top.to_taffy(rem_size),
right: self.right.to_taffy(rem_size),
bottom: self.bottom.to_taffy(rem_size),
left: self.left.to_taffy(rem_size),
}
}
pub fn to_pixels(&self, rem_size: f32) -> Edges<f32> {
Edges {
top: self.top.to_pixels(rem_size),
right: self.right.to_pixels(rem_size),
bottom: self.bottom.to_pixels(rem_size),
left: self.left.to_pixels(rem_size),
}
}
}
impl Edges<f32> {
pub fn is_empty(&self) -> bool {
self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
}
}
#[derive(Clone, Copy)]
pub enum AbsoluteLength {
Pixels(f32),
Rems(f32),
}
impl std::fmt::Debug for AbsoluteLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels),
AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems),
}
}
}
impl AbsoluteLength {
pub fn to_pixels(&self, rem_size: f32) -> f32 {
match self {
@ -295,6 +344,13 @@ impl AbsoluteLength {
AbsoluteLength::Rems(rems) => rems * rem_size,
}
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
match self {
AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
}
}
}
impl Default for AbsoluteLength {
@ -307,7 +363,7 @@ impl Default for AbsoluteLength {
#[derive(Clone, Copy)]
pub enum DefiniteLength {
Absolute(AbsoluteLength),
Relative(f32), // Percent, from 0 to 100.
Relative(f32), // 0. to 1.
}
impl DefiniteLength {
@ -326,6 +382,15 @@ impl DefiniteLength {
}
}
impl std::fmt::Debug for DefiniteLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f),
DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32),
}
}
}
impl From<AbsoluteLength> for DefiniteLength {
fn from(length: AbsoluteLength) -> Self {
Self::Absolute(length)
@ -345,6 +410,15 @@ pub enum Length {
Auto,
}
impl std::fmt::Debug for Length {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
Length::Auto => write!(f, "auto"),
}
}
}
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
DefiniteLength::Relative(fraction).into()
}
@ -387,3 +461,9 @@ impl Default for Length {
Self::Definite(DefiniteLength::default())
}
}
impl From<()> for Length {
fn from(_: ()) -> Self {
Self::Definite(DefiniteLength::default())
}
}

View File

@ -1,4 +1,5 @@
mod app;
mod image_cache;
pub use app::*;
mod assets;
#[cfg(any(test, feature = "test-support"))]
@ -8,6 +9,7 @@ pub mod elements;
pub mod font_cache;
mod image_data;
pub use crate::image_data::ImageData;
pub use taffy;
pub mod views;
pub use font_cache::FontCache;
mod clipboard;
@ -27,9 +29,9 @@ pub mod json;
pub mod keymap_matcher;
pub mod platform;
pub use gpui_macros::{test, Element};
pub use usvg;
pub use window::{
Axis, EngineLayout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt,
WindowContext,
Axis, Layout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, WindowContext,
};
pub use anyhow;

View File

@ -0,0 +1,99 @@
use std::sync::Arc;
use crate::ImageData;
use collections::HashMap;
use futures::{
future::{BoxFuture, Shared},
AsyncReadExt, FutureExt,
};
use image::ImageError;
use parking_lot::Mutex;
use thiserror::Error;
use util::{
arc_cow::ArcCow,
http::{self, HttpClient},
};
#[derive(Debug, Error, Clone)]
pub enum Error {
#[error("http error: {0}")]
Client(#[from] http::Error),
#[error("IO error: {0}")]
Io(Arc<std::io::Error>),
#[error("unexpected http status: {status}, body: {body}")]
BadStatus {
status: http::StatusCode,
body: String,
},
#[error("image error: {0}")]
Image(Arc<ImageError>),
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::Io(Arc::new(error))
}
}
impl From<ImageError> for Error {
fn from(error: ImageError) -> Self {
Error::Image(Arc::new(error))
}
}
pub struct ImageCache {
client: Arc<dyn HttpClient>,
images: Arc<Mutex<HashMap<ArcCow<'static, str>, FetchImageFuture>>>,
}
type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
impl ImageCache {
pub fn new(client: Arc<dyn HttpClient>) -> Self {
ImageCache {
client,
images: Default::default(),
}
}
pub fn get(
&self,
uri: impl Into<ArcCow<'static, str>>,
) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
let uri = uri.into();
let mut images = self.images.lock();
match images.get(uri.as_ref()) {
Some(future) => future.clone(),
None => {
let client = self.client.clone();
let future = {
let uri = uri.clone();
async move {
let mut response = client.get(uri.as_ref(), ().into(), true).await?;
let mut body = Vec::new();
response.body_mut().read_to_end(&mut body).await?;
if !response.status().is_success() {
return Err(Error::BadStatus {
status: response.status(),
body: String::from_utf8_lossy(&body).into_owned(),
});
}
let format = image::guess_format(&body)?;
let image =
image::load_from_memory_with_format(&body, format)?.into_bgra8();
Ok(ImageData::new(image))
}
}
.boxed()
.shared();
images.insert(uri, future.clone());
future
}
}
}
}

View File

@ -146,6 +146,7 @@ pub trait Window {
fn titlebar_height(&self) -> f32;
fn appearance(&self) -> Appearance;
fn screen(&self) -> Rc<dyn Screen>;
fn mouse_position(&self) -> Vector2F;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);

View File

@ -37,6 +37,7 @@ use objc::{
runtime::{Class, Object, Sel},
sel, sel_impl,
};
use postage::oneshot;
use ptr::null_mut;
use std::{

View File

@ -577,7 +577,6 @@ impl Renderer {
};
for (ix, quad) in quads.iter().enumerate() {
let bounds = quad.bounds * scale_factor;
let border_width = quad.border.width * scale_factor;
let shader_quad = shaders::GPUIQuad {
origin: bounds.origin().round().to_float2(),
size: bounds.size().round().to_float2(),
@ -585,10 +584,10 @@ impl Renderer {
.background
.unwrap_or_else(Color::transparent_black)
.to_uchar4(),
border_top: border_width * (quad.border.top as usize as f32),
border_right: border_width * (quad.border.right as usize as f32),
border_bottom: border_width * (quad.border.bottom as usize as f32),
border_left: border_width * (quad.border.left as usize as f32),
border_top: quad.border.top * scale_factor,
border_right: quad.border.right * scale_factor,
border_bottom: quad.border.bottom * scale_factor,
border_left: quad.border.left * scale_factor,
border_color: quad.border.color.to_uchar4(),
corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
@ -746,7 +745,6 @@ impl Renderer {
let origin = image.bounds.origin() * scale_factor;
let target_size = image.bounds.size() * scale_factor;
let corner_radii = image.corner_radii * scale_factor;
let border_width = image.border.width * scale_factor;
let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
images_by_atlas
.entry(alloc_id.atlas_id)
@ -756,10 +754,10 @@ impl Renderer {
target_size: target_size.to_float2(),
source_size: atlas_bounds.size().to_float2(),
atlas_origin: atlas_bounds.origin().to_float2(),
border_top: border_width * (image.border.top as usize as f32),
border_right: border_width * (image.border.right as usize as f32),
border_bottom: border_width * (image.border.bottom as usize as f32),
border_left: border_width * (image.border.left as usize as f32),
border_top: image.border.top * scale_factor,
border_right: image.border.right * scale_factor,
border_bottom: image.border.bottom * scale_factor,
border_left: image.border.left * scale_factor,
border_color: image.border.color.to_uchar4(),
corner_radius_top_left: corner_radii.top_left,
corner_radius_top_right: corner_radii.top_right,

View File

@ -202,6 +202,10 @@ impl platform::Window for StatusItem {
}
}
fn mouse_position(&self) -> Vector2F {
unimplemented!()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}

View File

@ -221,6 +221,14 @@ unsafe fn build_classes() {
};
}
pub fn convert_mouse_position(position: NSPoint, window_height: f32) -> Vector2F {
vec2f(
position.x as f32,
// MacOS screen coordinates are relative to bottom left
window_height - position.y as f32,
)
}
unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
let mut decl = ClassDecl::new(name, superclass).unwrap();
decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
@ -661,6 +669,16 @@ impl platform::Window for MacWindow {
}
}
fn mouse_position(&self) -> Vector2F {
let position = unsafe {
self.0
.borrow()
.native_window
.mouseLocationOutsideOfEventStream()
};
convert_mouse_position(position, self.content_size().y())
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
@ -988,7 +1006,40 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
.flatten()
.is_some();
if !is_composing {
handled = callback(Event::KeyDown(event));
// if the IME has changed the key, we'll first emit an event with the character
// generated by the IME system; then fallback to the keystroke if that is not
// handled.
// cases that we have working:
// - " on a brazillian layout by typing <quote><space>
// - ctrl-` on a brazillian layout by typing <ctrl-`>
// - $ on a czech QWERTY layout by typing <alt-4>
// - 4 on a czech QWERTY layout by typing <shift-4>
// - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
let event_with_ime_text = KeyDownEvent {
is_held: false,
keystroke: Keystroke {
// we match ctrl because some use-cases need it.
// we don't match alt because it's often used to generate the optional character
// we don't match shift because we're not here with letters (usually)
// we don't match cmd/fn because they don't seem to use IME
ctrl: event.keystroke.ctrl,
alt: false,
shift: false,
cmd: false,
function: false,
key: ime_text.clone().unwrap(),
},
};
handled = callback(Event::KeyDown(event_with_ime_text));
}
if !handled {
// empty key happens when you type a deadkey in input composition.
// (e.g. on a brazillian keyboard typing quote is a deadkey)
if !event.keystroke.key.is_empty() {
handled = callback(Event::KeyDown(event));
}
}
}
if !handled {

View File

@ -332,6 +332,10 @@ impl super::Window for Window {
Rc::new(Screen)
}
fn mouse_position(&self) -> Vector2F {
Vector2F::zero()
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}

View File

@ -8,7 +8,6 @@ use derive_more::Mul;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use serde_json::json;
use std::{
any::{Any, TypeId},
borrow::Cow,
@ -20,7 +19,6 @@ use crate::{
color::Color,
fonts::{FontId, GlyphId},
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
platform::{current::Surface, CursorStyle},
ImageData, WindowContext,
};
@ -28,10 +26,9 @@ pub use mouse_event::*;
pub use mouse_region::*;
pub struct SceneBuilder {
scale_factor: f32,
stacking_contexts: Vec<StackingContext>,
active_stacking_context_stack: Vec<usize>,
/// Used by the playground crate.
/// Used by the gpui2 crate.
pub event_handlers: Vec<EventHandler>,
#[cfg(debug_assertions)]
mouse_region_ids: HashSet<MouseRegionId>,
@ -171,15 +168,13 @@ pub struct Icon {
pub color: Color,
}
#[derive(Clone, Copy, Default, Debug, JsonSchema)]
#[derive(Clone, Copy, Default, Debug)]
pub struct Border {
pub width: f32,
pub color: Color,
pub overlay: bool,
pub top: bool,
pub right: bool,
pub bottom: bool,
pub left: bool,
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
}
#[derive(Clone, Copy, Default, Debug)]
@ -191,47 +186,6 @@ pub struct Underline {
pub squiggly: bool,
}
impl<'de> Deserialize<'de> for Border {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct BorderData {
pub width: f32,
pub color: Color,
#[serde(default)]
pub overlay: bool,
#[serde(default)]
pub top: bool,
#[serde(default)]
pub right: bool,
#[serde(default)]
pub bottom: bool,
#[serde(default)]
pub left: bool,
}
let data = BorderData::deserialize(deserializer)?;
let mut border = Border {
width: data.width,
color: data.color,
overlay: data.overlay,
top: data.top,
bottom: data.bottom,
left: data.left,
right: data.right,
};
if !border.top && !border.bottom && !border.left && !border.right {
border.top = true;
border.bottom = true;
border.left = true;
border.right = true;
}
Ok(border)
}
}
#[derive(Debug)]
pub struct Path {
pub bounds: RectF,
@ -290,45 +244,40 @@ impl Scene {
}
impl SceneBuilder {
pub fn new(scale_factor: f32) -> Self {
let stacking_context = StackingContext::new(None, 0);
SceneBuilder {
scale_factor,
stacking_contexts: vec![stacking_context],
active_stacking_context_stack: vec![0],
pub fn new() -> Self {
let mut this = SceneBuilder {
stacking_contexts: Vec::new(),
active_stacking_context_stack: Vec::new(),
#[cfg(debug_assertions)]
mouse_region_ids: Default::default(),
mouse_region_ids: HashSet::default(),
event_handlers: Vec::new(),
}
};
this.clear();
this
}
pub fn build(mut self) -> Scene {
self.stacking_contexts
.sort_by_key(|context| context.z_index);
pub fn clear(&mut self) {
self.stacking_contexts.clear();
self.stacking_contexts.push(StackingContext::new(None, 0));
self.active_stacking_context_stack.clear();
self.active_stacking_context_stack.push(0);
#[cfg(debug_assertions)]
self.mouse_region_ids.clear();
}
pub fn build(&mut self, scale_factor: f32) -> Scene {
let mut stacking_contexts = std::mem::take(&mut self.stacking_contexts);
stacking_contexts.sort_by_key(|context| context.z_index);
let event_handlers = std::mem::take(&mut self.event_handlers);
self.clear();
Scene {
scale_factor: self.scale_factor,
stacking_contexts: self.stacking_contexts,
event_handlers: self.event_handlers,
scale_factor,
stacking_contexts,
event_handlers,
}
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub fn paint_stacking_context<F>(
&mut self,
clip_bounds: Option<RectF>,
z_index: Option<usize>,
f: F,
) where
F: FnOnce(&mut Self),
{
self.push_stacking_context(clip_bounds, z_index);
f(self);
self.pop_stacking_context();
}
pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
self.active_stacking_context_stack
@ -342,15 +291,6 @@ impl SceneBuilder {
assert!(!self.active_stacking_context_stack.is_empty());
}
pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
where
F: FnOnce(&mut Self),
{
self.push_layer(clip_bounds);
f(self);
self.pop_layer();
}
pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
self.active_stacking_context().push_layer(clip_bounds);
}
@ -606,99 +546,6 @@ impl Layer {
}
}
impl Border {
pub fn new(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: false,
left: false,
bottom: false,
right: false,
}
}
pub fn all(width: f32, color: Color) -> Self {
Self {
width,
color,
overlay: false,
top: true,
left: true,
bottom: true,
right: true,
}
}
pub fn top(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.top = true;
border
}
pub fn left(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.left = true;
border
}
pub fn bottom(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.bottom = true;
border
}
pub fn right(width: f32, color: Color) -> Self {
let mut border = Self::new(width, color);
border.right = true;
border
}
pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
self.top = top;
self.left = left;
self.bottom = bottom;
self.right = right;
self
}
pub fn top_width(&self) -> f32 {
if self.top {
self.width
} else {
0.0
}
}
pub fn left_width(&self) -> f32 {
if self.left {
self.width
} else {
0.0
}
}
}
impl ToJson for Border {
fn to_json(&self) -> serde_json::Value {
let mut value = json!({});
if self.top {
value["top"] = json!(self.width);
}
if self.right {
value["right"] = json!(self.width);
}
if self.bottom {
value["bottom"] = json!(self.width);
}
if self.left {
value["left"] = json!(self.width);
}
value
}
}
impl MouseRegion {
pub fn id(&self) -> MouseRegionId {
self.id

View File

@ -9,7 +9,6 @@ use crate::{
platform::FontSystem,
scene,
window::WindowContext,
SceneBuilder,
};
use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@ -284,7 +283,6 @@ impl Line {
pub fn paint(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
@ -347,7 +345,7 @@ impl Line {
}
if let Some((underline_origin, underline_style)) = finished_underline {
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@ -357,14 +355,14 @@ impl Line {
}
if glyph.is_emoji {
scene.push_image_glyph(scene::ImageGlyph {
cx.scene().push_image_glyph(scene::ImageGlyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_origin,
});
} else {
scene.push_glyph(scene::Glyph {
cx.scene().push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
@ -377,7 +375,7 @@ impl Line {
if let Some((underline_start, underline_style)) = underline.take() {
let line_end_x = origin.x() + self.layout.width;
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_start,
width: line_end_x - underline_start.x(),
color: underline_style.color.unwrap(),
@ -389,7 +387,6 @@ impl Line {
pub fn paint_wrapped(
&self,
scene: &mut SceneBuilder,
origin: Vector2F,
visible_bounds: RectF,
line_height: f32,
@ -417,7 +414,7 @@ impl Line {
{
boundaries.next();
if let Some((underline_origin, underline_style)) = underline {
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@ -461,7 +458,7 @@ impl Line {
}
if let Some((underline_origin, underline_style)) = finished_underline {
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: glyph_origin.x() - underline_origin.x(),
thickness: underline_style.thickness.into(),
@ -477,14 +474,14 @@ impl Line {
);
if glyph_bounds.intersects(visible_bounds) {
if glyph.is_emoji {
scene.push_image_glyph(scene::ImageGlyph {
cx.scene().push_image_glyph(scene::ImageGlyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_bounds.origin() + baseline_offset,
});
} else {
scene.push_glyph(scene::Glyph {
cx.scene().push_glyph(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
@ -498,7 +495,7 @@ impl Line {
if let Some((underline_origin, underline_style)) = underline.take() {
let line_end_x = glyph_origin.x() + self.layout.width - prev_position;
scene.push_underline(scene::Underline {
cx.scene().push_underline(scene::Underline {
origin: underline_origin,
width: line_end_x - underline_origin.x(),
thickness: underline_style.thickness.into(),

32
crates/gpui2/Cargo.toml Normal file
View File

@ -0,0 +1,32 @@
[package]
name = "gpui2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "gpui2"
path = "src/gpui2.rs"
[features]
test-support = ["gpui/test-support"]
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
gpui = { path = "../gpui" }
log.workspace = true
futures.workspace = true
gpui2_macros = { path = "../gpui2_macros" }
parking_lot.workspace = true
refineable.workspace = true
rust-embed.workspace = true
serde.workspace = true
settings = { path = "../settings" }
simplelog = "0.9"
smallvec.workspace = true
theme = { path = "../theme" }
util = { path = "../util" }
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }

View File

@ -1,8 +1,8 @@
use crate::{layout_context::LayoutContext, paint_context::PaintContext};
use crate::ViewContext;
use gpui::{geometry::rect::RectF, LayoutEngine, LayoutId};
use util::ResultExt;
/// Makes a new, playground-style element into a legacy element.
/// Makes a new, gpui2-style element into a legacy element.
pub struct AdapterElement<V>(pub(crate) crate::element::AnyElement<V>);
impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
@ -13,12 +13,11 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut gpui::LayoutContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
cx.push_layout_engine(LayoutEngine::new());
let size = constraint.max;
let mut cx = LayoutContext::new(cx);
let mut cx = ViewContext::new(cx);
let layout_id = self.0.layout(view, &mut cx).log_err();
if let Some(layout_id) = layout_id {
cx.layout_engine()
@ -37,41 +36,40 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: RectF,
visible_bounds: RectF,
_visible_bounds: RectF,
layout_data: &mut Option<(LayoutEngine, LayoutId)>,
view: &mut V,
legacy_cx: &mut gpui::PaintContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> Self::PaintState {
let (layout_engine, layout_id) = layout_data.take().unwrap();
legacy_cx.push_layout_engine(layout_engine);
let mut cx = PaintContext::new(legacy_cx, scene);
self.0.paint(view, &mut cx);
*layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id));
cx.push_layout_engine(layout_engine);
self.0
.paint(view, bounds.origin(), &mut ViewContext::new(cx));
*layout_data = cx.pop_layout_engine().zip(Some(layout_id));
debug_assert!(layout_data.is_some());
}
fn rect_for_text_range(
&self,
range_utf16: std::ops::Range<usize>,
bounds: RectF,
visible_bounds: RectF,
layout: &Self::LayoutState,
paint: &Self::PaintState,
view: &V,
cx: &gpui::ViewContext<V>,
_range_utf16: std::ops::Range<usize>,
_bounds: RectF,
_visible_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
_view: &V,
_cx: &gpui::ViewContext<V>,
) -> Option<RectF> {
todo!("implement before merging to main")
}
fn debug(
&self,
bounds: RectF,
layout: &Self::LayoutState,
paint: &Self::PaintState,
view: &V,
cx: &gpui::ViewContext<V>,
_bounds: RectF,
_layout: &Self::LayoutState,
_paint: &Self::PaintState,
_view: &V,
_cx: &gpui::ViewContext<V>,
) -> gpui::serde_json::Value {
todo!("implement before merging to main")
}

View File

@ -1,8 +1,9 @@
#![allow(dead_code)]
use std::{num::ParseIntError, ops::Range};
use serde::de::{self, Deserialize, Deserializer, Visitor};
use smallvec::SmallVec;
use std::fmt;
use std::{num::ParseIntError, ops::Range};
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
@ -19,6 +20,40 @@ pub struct Rgba {
pub a: f32,
}
struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor {
type Value = Rgba;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
if value.len() == 7 || value.len() == 9 {
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
let a = if value.len() == 9 {
u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
} else {
1.0
};
Ok(Rgba { r, g, b, a })
} else {
Err(E::custom(
"Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
))
}
}
}
impl<'de> Deserialize<'de> for Rgba {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(RgbaVisitor)
}
}
pub trait Lerp {
fn lerp(&self, level: f32) -> Hsla;
}
@ -219,6 +254,19 @@ impl Into<gpui::color::Color> for Hsla {
}
}
impl<'de> Deserialize<'de> for Hsla {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// First, deserialize it into Rgba
let rgba = Rgba::deserialize(deserializer)?;
// Then, use the From<Rgba> for Hsla implementation to convert it
Ok(Hsla::from(rgba))
}
}
pub struct ColorScale {
colors: SmallVec<[Hsla; 2]>,
positions: SmallVec<[f32; 2]>,

186
crates/gpui2/src/element.rs Normal file
View File

@ -0,0 +1,186 @@
pub use crate::ViewContext;
use anyhow::Result;
use gpui::geometry::vector::Vector2F;
pub use gpui::{Layout, LayoutId};
use smallvec::SmallVec;
pub trait Element<V: 'static>: 'static + IntoElement<V> {
type PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized;
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized;
fn into_any(self) -> AnyElement<V>
where
Self: 'static + Sized,
{
AnyElement(Box::new(StatefulElement {
element: self,
phase: ElementPhase::Init,
}))
}
}
/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
trait AnyStatefulElement<V> {
fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId>;
fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>);
}
/// A wrapper around an element that stores its layout state.
struct StatefulElement<V: 'static, E: Element<V>> {
element: E,
phase: ElementPhase<V, E>,
}
enum ElementPhase<V: 'static, E: Element<V>> {
Init,
PostLayout {
layout_id: LayoutId,
paint_state: E::PaintState,
},
#[allow(dead_code)]
PostPaint {
layout: Layout,
paint_state: E::PaintState,
},
Error(String),
}
impl<V: 'static, E: Element<V>> std::fmt::Debug for ElementPhase<V, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ElementPhase::Init => write!(f, "Init"),
ElementPhase::PostLayout { layout_id, .. } => {
write!(f, "PostLayout with layout id: {:?}", layout_id)
}
ElementPhase::PostPaint { layout, .. } => {
write!(f, "PostPaint with layout: {:?}", layout)
}
ElementPhase::Error(err) => write!(f, "Error: {}", err),
}
}
}
impl<V: 'static, E: Element<V>> Default for ElementPhase<V, E> {
fn default() -> Self {
Self::Init
}
}
/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
impl<V, E: Element<V>> AnyStatefulElement<V> for StatefulElement<V, E> {
fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId> {
let result;
self.phase = match self.element.layout(view, cx) {
Ok((layout_id, paint_state)) => {
result = Ok(layout_id);
ElementPhase::PostLayout {
layout_id,
paint_state,
}
}
Err(error) => {
let message = error.to_string();
result = Err(error);
ElementPhase::Error(message)
}
};
result
}
fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
self.phase = match std::mem::take(&mut self.phase) {
ElementPhase::PostLayout {
layout_id,
mut paint_state,
} => match cx.computed_layout(layout_id) {
Ok(layout) => {
self.element
.paint(view, parent_origin, &layout, &mut paint_state, cx);
ElementPhase::PostPaint {
layout,
paint_state,
}
}
Err(error) => ElementPhase::Error(error.to_string()),
},
ElementPhase::PostPaint {
layout,
mut paint_state,
} => {
self.element
.paint(view, parent_origin, &layout, &mut paint_state, cx);
ElementPhase::PostPaint {
layout,
paint_state,
}
}
phase @ ElementPhase::Error(_) => phase,
phase @ _ => {
panic!("invalid element phase to call paint: {:?}", phase);
}
};
}
}
/// A dynamic element.
pub struct AnyElement<V>(Box<dyn AnyStatefulElement<V>>);
impl<V> AnyElement<V> {
pub fn layout(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> Result<LayoutId> {
self.0.layout(view, cx)
}
pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<V>) {
self.0.paint(view, parent_origin, cx)
}
}
pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
fn child(mut self, child: impl IntoElement<V>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_element().into_any());
self
}
fn children<I, E>(mut self, children: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoElement<V>,
Self: Sized,
{
self.children_mut().extend(
children
.into_iter()
.map(|child| child.into_element().into_any()),
);
self
}
}
pub trait IntoElement<V: 'static> {
type Element: Element<V>;
fn into_element(self) -> Self::Element;
}

View File

@ -0,0 +1,10 @@
pub mod div;
pub mod hoverable;
mod img;
pub mod pressable;
pub mod svg;
pub mod text;
pub use div::div;
pub use img::img;
pub use svg::svg;

View File

@ -0,0 +1,320 @@
use std::{cell::Cell, rc::Rc};
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
hsla,
style::{CornerRadii, Overflow, Style, StyleHelpers, Styleable},
InteractionHandlers, Interactive, ViewContext,
};
use anyhow::Result;
use gpui::{
geometry::{rect::RectF, vector::Vector2F, Point},
platform::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent},
scene::{self},
LayoutId,
};
use refineable::{Refineable, RefinementCascade};
use smallvec::SmallVec;
use util::ResultExt;
pub struct Div<V: 'static> {
styles: RefinementCascade<Style>,
handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>,
scroll_state: Option<ScrollState>,
}
pub fn div<V>() -> Div<V> {
Div {
styles: Default::default(),
handlers: Default::default(),
children: Default::default(),
scroll_state: None,
}
}
impl<V: 'static> Element<V> for Div<V> {
type PaintState = Vec<LayoutId>;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
let style = self.computed_style();
let pop_text_style = style.text_style(cx).map_or(false, |style| {
cx.push_text_style(&style).log_err().is_some()
});
let children = self
.children
.iter_mut()
.map(|child| child.layout(view, cx))
.collect::<Result<Vec<LayoutId>>>()?;
if pop_text_style {
cx.pop_text_style();
}
Ok((cx.add_layout_node(style, children.clone())?, children))
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
child_layouts: &mut Vec<LayoutId>,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let order = layout.order;
let bounds = layout.bounds + parent_origin;
let style = self.computed_style();
let pop_text_style = style.text_style(cx).map_or(false, |style| {
cx.push_text_style(&style).log_err().is_some()
});
style.paint_background(bounds, cx);
self.interaction_handlers().paint(order, bounds, cx);
let scrolled_origin = bounds.origin() - self.scroll_offset(&style.overflow);
// TODO: Support only one dimension being hidden
let mut pop_layer = false;
if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
cx.scene().push_layer(Some(bounds));
pop_layer = true;
}
for child in &mut self.children {
child.paint(view, scrolled_origin, cx);
}
if pop_layer {
cx.scene().pop_layer();
}
style.paint_foreground(bounds, cx);
if pop_text_style {
cx.pop_text_style();
}
self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
if cx.is_inspector_enabled() {
self.paint_inspector(parent_origin, layout, cx);
}
}
}
impl<V: 'static> Div<V> {
pub fn overflow_hidden(mut self) -> Self {
self.declared_style().overflow.x = Some(Overflow::Hidden);
self.declared_style().overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_x(mut self) -> Self {
self.declared_style().overflow.x = Some(Overflow::Hidden);
self
}
pub fn overflow_hidden_y(mut self) -> Self {
self.declared_style().overflow.y = Some(Overflow::Hidden);
self
}
pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.x = Some(Overflow::Scroll);
self.declared_style().overflow.y = Some(Overflow::Scroll);
self
}
pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.x = Some(Overflow::Scroll);
self
}
pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
self.scroll_state = Some(scroll_state);
self.declared_style().overflow.y = Some(Overflow::Scroll);
self
}
fn scroll_offset(&self, overflow: &Point<Overflow>) -> Vector2F {
let mut offset = Vector2F::zero();
if overflow.y == Overflow::Scroll {
offset.set_y(self.scroll_state.as_ref().unwrap().y());
}
if overflow.x == Overflow::Scroll {
offset.set_x(self.scroll_state.as_ref().unwrap().x());
}
offset
}
fn handle_scroll(
&mut self,
order: u32,
bounds: RectF,
overflow: Point<Overflow>,
child_layout_ids: &[LayoutId],
cx: &mut ViewContext<V>,
) {
if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
let mut scroll_max = Vector2F::zero();
for child_layout_id in child_layout_ids {
if let Some(child_layout) = cx
.layout_engine()
.unwrap()
.computed_layout(*child_layout_id)
.log_err()
{
scroll_max = scroll_max.max(child_layout.bounds.lower_right());
}
}
scroll_max -= bounds.size();
let scroll_state = self.scroll_state.as_ref().unwrap().clone();
cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
if bounds.contains_point(event.position) {
let scroll_delta = match event.delta {
gpui::platform::ScrollDelta::Pixels(delta) => delta,
gpui::platform::ScrollDelta::Lines(delta) => {
delta * cx.text_style().font_size
}
};
if overflow.x == Overflow::Scroll {
scroll_state.set_x(
(scroll_state.x() - scroll_delta.x())
.max(0.)
.min(scroll_max.x()),
);
}
if overflow.y == Overflow::Scroll {
scroll_state.set_y(
(scroll_state.y() - scroll_delta.y())
.max(0.)
.min(scroll_max.y()),
);
}
cx.repaint();
} else {
cx.bubble_event();
}
})
}
}
fn paint_inspector(&self, parent_origin: Vector2F, layout: &Layout, cx: &mut ViewContext<V>) {
let style = self.styles.merged();
let bounds = layout.bounds + parent_origin;
let hovered = bounds.contains_point(cx.mouse_position());
if hovered {
let rem_size = cx.rem_size();
cx.scene().push_quad(scene::Quad {
bounds,
background: Some(hsla(0., 0., 1., 0.05).into()),
border: gpui::Border {
color: hsla(0., 0., 1., 0.2).into(),
top: 1.,
right: 1.,
bottom: 1.,
left: 1.,
},
corner_radii: CornerRadii::default()
.refined(&style.corner_radii)
.to_gpui(bounds.size(), rem_size),
})
}
let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
if bounds.contains_point(event.position) {
if event.is_down {
pressed.set(true);
} else if pressed.get() {
pressed.set(false);
eprintln!("clicked div {:?} {:#?}", bounds, style);
}
}
});
let hovered = Cell::new(hovered);
cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
cx.bubble_event();
let hovered_now = bounds.contains_point(event.position);
if hovered.get() != hovered_now {
hovered.set(hovered_now);
cx.repaint();
}
});
}
}
impl<V> Styleable for Div<V> {
type Style = Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.styles
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
self.styles.base()
}
}
impl<V> StyleHelpers for Div<V> {}
impl<V> Interactive<V> for Div<V> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
&mut self.handlers
}
}
impl<V: 'static> ParentElement<V> for Div<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
impl<V: 'static> IntoElement<V> for Div<V> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
#[derive(Default, Clone)]
pub struct ScrollState(Rc<Cell<Vector2F>>);
impl ScrollState {
pub fn x(&self) -> f32 {
self.0.get().x()
}
pub fn set_x(&self, value: f32) {
let mut current_value = self.0.get();
current_value.set_x(value);
self.0.set(current_value);
}
pub fn y(&self) -> f32 {
self.0.get().y()
}
pub fn set_y(&self, value: f32) {
let mut current_value = self.0.get();
current_value.set_y(value);
self.0.set(current_value);
}
}

View File

@ -0,0 +1,105 @@
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
style::{Style, StyleHelpers, Styleable},
ViewContext,
};
use anyhow::Result;
use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
pub struct Hoverable<E: Styleable> {
hovered: Rc<Cell<bool>>,
cascade_slot: CascadeSlot,
hovered_style: <E::Style as Refineable>::Refinement,
child: E,
}
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
Hoverable {
hovered: Rc::new(Cell::new(false)),
cascade_slot: child.style_cascade().reserve(),
hovered_style: Default::default(),
child,
}
}
impl<E: Styleable> Styleable for Hoverable<E> {
type Style = E::Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
self.child.style_cascade()
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.hovered_style
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
Ok(self.child.layout(view, cx)?)
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let bounds = layout.bounds + parent_origin;
self.hovered.set(bounds.contains_point(cx.mouse_position()));
let slot = self.cascade_slot;
let style = self.hovered.get().then_some(self.hovered_style.clone());
self.style_cascade().set(slot, style);
let hovered = self.hovered.clone();
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
cx.bubble_event();
if bounds.contains_point(cx.mouse_position()) != hovered.get() {
cx.repaint();
}
});
self.child
.paint(view, parent_origin, layout, paint_state, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
self.child.interaction_handlers()
}
}
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
self.child.children_mut()
}
}
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

View File

@ -0,0 +1,110 @@
use crate as gpui2;
use crate::{
style::{Style, StyleHelpers, Styleable},
Element,
};
use futures::FutureExt;
use gpui::geometry::vector::Vector2F;
use gpui::scene;
use gpui2_macros::IntoElement;
use refineable::RefinementCascade;
use util::arc_cow::ArcCow;
use util::ResultExt;
#[derive(IntoElement)]
pub struct Img {
style: RefinementCascade<Style>,
uri: Option<ArcCow<'static, str>>,
}
pub fn img() -> Img {
Img {
style: RefinementCascade::default(),
uri: None,
}
}
impl Img {
pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
self.uri = Some(uri.into());
self
}
}
impl<V: 'static> Element<V> for Img {
type PaintState = ();
fn layout(
&mut self,
_: &mut V,
cx: &mut crate::ViewContext<V>,
) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
where
Self: Sized,
{
let style = self.computed_style();
let layout_id = cx.add_layout_node(style, [])?;
Ok((layout_id, ()))
}
fn paint(
&mut self,
_: &mut V,
parent_origin: Vector2F,
layout: &gpui::Layout,
_: &mut Self::PaintState,
cx: &mut crate::ViewContext<V>,
) where
Self: Sized,
{
let style = self.computed_style();
let bounds = layout.bounds + parent_origin;
style.paint_background(bounds, cx);
if let Some(uri) = &self.uri {
let image_future = cx.image_cache.get(uri.clone());
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(ResultExt::log_err)
{
let rem_size = cx.rem_size();
cx.scene().push_image(scene::Image {
bounds,
border: gpui::Border {
color: style.border_color.unwrap_or_default().into(),
top: style.border_widths.top.to_pixels(rem_size),
right: style.border_widths.right.to_pixels(rem_size),
bottom: style.border_widths.bottom.to_pixels(rem_size),
left: style.border_widths.left.to_pixels(rem_size),
},
corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
grayscale: false,
data,
})
} else {
cx.spawn(|this, mut cx| async move {
if image_future.await.log_err().is_some() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
}
})
.detach();
}
}
}
}
impl Styleable for Img {
type Style = Style;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.style
}
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
self.style.base()
}
}
impl StyleHelpers for Img {}

View File

@ -0,0 +1,108 @@
use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement},
interactive::{InteractionHandlers, Interactive},
style::{Style, StyleHelpers, Styleable},
ViewContext,
};
use anyhow::Result;
use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
pub struct Pressable<E: Styleable> {
pressed: Rc<Cell<bool>>,
pressed_style: <E::Style as Refineable>::Refinement,
cascade_slot: CascadeSlot,
child: E,
}
pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
Pressable {
pressed: Rc::new(Cell::new(false)),
pressed_style: Default::default(),
cascade_slot: child.style_cascade().reserve(),
child,
}
}
impl<E: Styleable> Styleable for Pressable<E> {
type Style = E::Style;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.pressed_style
}
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
self.child.style_cascade()
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
type PaintState = E::PaintState;
fn layout(
&mut self,
view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
self.child.layout(view, cx)
}
fn paint(
&mut self,
view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{
let slot = self.cascade_slot;
let style = self.pressed.get().then_some(self.pressed_style.clone());
self.style_cascade().set(slot, style);
let pressed = self.pressed.clone();
let bounds = layout.bounds + parent_origin;
cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
if event.is_down {
if bounds.contains_point(event.position) {
pressed.set(true);
cx.repaint();
}
} else if pressed.get() {
pressed.set(false);
cx.repaint();
}
});
self.child
.paint(view, parent_origin, layout, paint_state, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
self.child.interaction_handlers()
}
}
impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
self.child.children_mut()
}
}
impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}

View File

@ -0,0 +1,84 @@
use crate::{
self as gpui2, scene,
style::{Style, StyleHelpers, Styleable},
Element, IntoElement, Layout, LayoutId, Rgba,
};
use gpui::geometry::vector::Vector2F;
use refineable::RefinementCascade;
use std::borrow::Cow;
use util::ResultExt;
#[derive(IntoElement)]
pub struct Svg {
path: Option<Cow<'static, str>>,
style: RefinementCascade<Style>,
}
pub fn svg() -> Svg {
Svg {
path: None,
style: RefinementCascade::<Style>::default(),
}
}
impl Svg {
pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
self.path = Some(path.into());
self
}
}
impl<V: 'static> Element<V> for Svg {
type PaintState = ();
fn layout(
&mut self,
_: &mut V,
cx: &mut crate::ViewContext<V>,
) -> anyhow::Result<(LayoutId, Self::PaintState)>
where
Self: Sized,
{
let style = self.computed_style();
Ok((cx.add_layout_node(style, [])?, ()))
}
fn paint(
&mut self,
_: &mut V,
parent_origin: Vector2F,
layout: &Layout,
_: &mut Self::PaintState,
cx: &mut crate::ViewContext<V>,
) where
Self: Sized,
{
let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
let icon = scene::Icon {
bounds: layout.bounds + parent_origin,
svg: svg_tree,
path: path.clone(),
color: Rgba::from(fill_color).into(),
};
cx.scene().push_icon(icon);
}
}
}
}
impl Styleable for Svg {
type Style = Style;
fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
&mut self.style
}
fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
self.style.base()
}
}
impl StyleHelpers for Svg {}

View File

@ -0,0 +1,109 @@
use crate::{
element::{Element, IntoElement, Layout},
ViewContext,
};
use anyhow::Result;
use gpui::{
geometry::{vector::Vector2F, Size},
text_layout::LineLayout,
LayoutId,
};
use parking_lot::Mutex;
use std::sync::Arc;
use util::arc_cow::ArcCow;
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
type Element = Text;
fn into_element(self) -> Self::Element {
Text { text: self.into() }
}
}
pub struct Text {
text: ArcCow<'static, str>,
}
impl<V: 'static> Element<V> for Text {
type PaintState = Arc<Mutex<Option<TextLayout>>>;
fn layout(
&mut self,
_view: &mut V,
cx: &mut ViewContext<V>,
) -> Result<(LayoutId, Self::PaintState)> {
let fonts = cx.platform().fonts();
let text_style = cx.text_style();
let line_height = cx.font_cache().line_height(text_style.font_size);
let text = self.text.clone();
let paint_state = Arc::new(Mutex::new(None));
let layout_id = cx.add_measured_layout_node(Default::default(), {
let paint_state = paint_state.clone();
move |_params| {
let line_layout = fonts.layout_line(
text.as_ref(),
text_style.font_size,
&[(text.len(), text_style.to_run())],
);
let size = Size {
width: line_layout.width,
height: line_height,
};
paint_state.lock().replace(TextLayout {
line_layout: Arc::new(line_layout),
line_height,
});
size
}
});
Ok((layout_id?, paint_state))
}
fn paint<'a>(
&mut self,
_view: &mut V,
parent_origin: Vector2F,
layout: &Layout,
paint_state: &mut Self::PaintState,
cx: &mut ViewContext<V>,
) {
let bounds = layout.bounds + parent_origin;
let line_layout;
let line_height;
{
let paint_state = paint_state.lock();
let paint_state = paint_state
.as_ref()
.expect("measurement has not been performed");
line_layout = paint_state.line_layout.clone();
line_height = paint_state.line_height;
}
let text_style = cx.text_style();
let line =
gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
// TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
let visible_bounds = bounds;
line.paint(bounds.origin(), visible_bounds, line_height, cx.legacy_cx);
}
}
impl<V: 'static> IntoElement<V> for Text {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
pub struct TextLayout {
line_layout: Arc<LineLayout>,
line_height: f32,
}

22
crates/gpui2/src/gpui2.rs Normal file
View File

@ -0,0 +1,22 @@
pub mod adapter;
pub mod color;
pub mod element;
pub mod elements;
pub mod interactive;
pub mod style;
pub mod view;
pub mod view_context;
pub use color::*;
pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};
pub use geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
};
pub use gpui::*;
pub use gpui2_macros::{Element, *};
pub use interactive::*;
pub use platform::{Platform, WindowBounds, WindowOptions};
pub use util::arc_cow::ArcCow;
pub use view::*;
pub use view_context::ViewContext;

View File

@ -0,0 +1,165 @@
use gpui::{
geometry::rect::RectF,
platform::{MouseButton, MouseButtonEvent},
EventContext,
};
use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc};
use crate::ViewContext;
pub trait Interactive<V: 'static> {
fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
fn on_mouse_down(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_down
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_mouse_up(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_up
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_mouse_down_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_down_out
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_mouse_up_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.interaction_handlers()
.mouse_up_out
.push(Rc::new(move |view, event, cx| {
if event.button == button {
handler(view, event, cx)
}
}));
self
}
fn on_click(
self,
button: MouseButton,
handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
let pressed = Rc::new(Cell::new(false));
self.on_mouse_down(button, {
let pressed = pressed.clone();
move |_, _, _| {
pressed.set(true);
}
})
.on_mouse_up_out(button, {
let pressed = pressed.clone();
move |_, _, _| {
pressed.set(false);
}
})
.on_mouse_up(button, move |view, event, cx| {
if pressed.get() {
pressed.set(false);
handler(view, event, cx);
}
})
}
}
pub struct InteractionHandlers<V: 'static> {
mouse_down: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
mouse_down_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
mouse_up: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
mouse_up_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
}
impl<V: 'static> InteractionHandlers<V> {
pub fn paint(&self, order: u32, bounds: RectF, cx: &mut ViewContext<V>) {
for handler in self.mouse_down.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if event.is_down && bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
for handler in self.mouse_up.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if !event.is_down && bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
for handler in self.mouse_down_out.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if event.is_down && !bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
for handler in self.mouse_up_out.iter().cloned() {
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if !event.is_down && !bounds.contains_point(event.position) {
handler(view, event, cx);
}
})
}
}
}
impl<V> Default for InteractionHandlers<V> {
fn default() -> Self {
Self {
mouse_down: Default::default(),
mouse_up: Default::default(),
mouse_down_out: Default::default(),
mouse_up_out: Default::default(),
}
}
}

604
crates/gpui2/src/style.rs Normal file
View File

@ -0,0 +1,604 @@
use crate::{
color::Hsla,
elements::hoverable::{hoverable, Hoverable},
elements::pressable::{pressable, Pressable},
ViewContext,
};
pub use fonts::Style as FontStyle;
pub use fonts::Weight as FontWeight;
pub use gpui::taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
};
use gpui::{
fonts::{self, TextStyleRefinement},
geometry::{
rect::RectF, relative, vector::Vector2F, AbsoluteLength, DefiniteLength, Edges,
EdgesRefinement, Length, Point, PointRefinement, Size, SizeRefinement,
},
scene, taffy, WindowContext,
};
use gpui2_macros::styleable_helpers;
use refineable::{Refineable, RefinementCascade};
use std::sync::Arc;
#[derive(Clone, Refineable, Debug)]
#[refineable(debug)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
// Overflow properties
/// How children overflowing their container should affect layout
#[refineable]
pub overflow: Point<Overflow>,
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
pub scrollbar_width: f32,
// Position properties
/// What should the `position` value of this struct use as a base offset?
pub position: Position,
/// How should the position of this element be tweaked relative to the layout defined?
#[refineable]
pub inset: Edges<Length>,
// Size properies
/// Sets the initial size of the item
#[refineable]
pub size: Size<Length>,
/// Controls the minimum size of the item
#[refineable]
pub min_size: Size<Length>,
/// Controls the maximum size of the item
#[refineable]
pub max_size: Size<Length>,
/// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
pub aspect_ratio: Option<f32>,
// Spacing Properties
/// How large should the margin be on each side?
#[refineable]
pub margin: Edges<Length>,
/// How large should the padding be on each side?
#[refineable]
pub padding: Edges<DefiniteLength>,
/// How large should the border be on each side?
#[refineable]
pub border_widths: Edges<AbsoluteLength>,
// Alignment properties
/// How this node's children aligned in the cross/block axis?
pub align_items: Option<AlignItems>,
/// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
pub align_self: Option<AlignSelf>,
/// How should content contained within this item be aligned in the cross/block axis
pub align_content: Option<AlignContent>,
/// How should contained within this item be aligned in the main/inline axis
pub justify_content: Option<JustifyContent>,
/// How large should the gaps between items in a flex container be?
#[refineable]
pub gap: Size<DefiniteLength>,
// Flexbox properies
/// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
/// Should elements wrap, or stay in a single line?
pub flex_wrap: FlexWrap,
/// Sets the initial main axis size of the item
pub flex_basis: Length,
/// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
pub flex_grow: f32,
/// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
pub flex_shrink: f32,
/// The fill color of this element
pub fill: Option<Fill>,
/// The border color of this element
pub border_color: Option<Hsla>,
/// The radius of the corners of this element
#[refineable]
pub corner_radii: CornerRadii,
/// The color of text within this element. Cascades to children unless overridden.
pub text_color: Option<Hsla>,
/// The font size in rems.
pub font_size: Option<f32>,
pub font_family: Option<Arc<str>>,
pub font_weight: Option<FontWeight>,
pub font_style: Option<FontStyle>,
}
impl Style {
pub fn text_style(&self, cx: &WindowContext) -> Option<TextStyleRefinement> {
if self.text_color.is_none()
&& self.font_size.is_none()
&& self.font_family.is_none()
&& self.font_weight.is_none()
&& self.font_style.is_none()
{
return None;
}
Some(TextStyleRefinement {
color: self.text_color.map(Into::into),
font_family: self.font_family.clone(),
font_size: self.font_size.map(|size| size * cx.rem_size()),
font_weight: self.font_weight,
font_style: self.font_style,
underline: None,
})
}
pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
taffy::style::Style {
display: self.display,
overflow: self.overflow.clone().into(),
scrollbar_width: self.scrollbar_width,
position: self.position,
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
max_size: self.max_size.to_taffy(rem_size),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border_widths.to_taffy(rem_size),
align_items: self.align_items,
align_self: self.align_self,
align_content: self.align_content,
justify_content: self.justify_content,
gap: self.gap.to_taffy(rem_size),
flex_direction: self.flex_direction,
flex_wrap: self.flex_wrap,
flex_basis: self.flex_basis.to_taffy(rem_size).into(),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
..Default::default() // Ignore grid properties for now
}
}
/// Paints the background of an element styled with this style.
pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
cx.scene().push_quad(gpui::Quad {
bounds,
background: Some(color.into()),
corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
border: Default::default(),
});
}
}
/// Paints the foreground of an element styled with this style.
pub fn paint_foreground<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size();
if let Some(color) = self.border_color {
let border = self.border_widths.to_pixels(rem_size);
if !border.is_empty() {
cx.scene().push_quad(gpui::Quad {
bounds,
background: None,
corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
border: scene::Border {
color: color.into(),
top: border.top,
right: border.right,
bottom: border.bottom,
left: border.left,
},
});
}
}
}
}
impl Default for Style {
fn default() -> Self {
Style {
display: Display::Block,
overflow: Point {
x: Overflow::Visible,
y: Overflow::Visible,
},
scrollbar_width: 0.0,
position: Position::Relative,
inset: Edges::auto(),
margin: Edges::<Length>::zero(),
padding: Edges::<DefiniteLength>::zero(),
border_widths: Edges::<AbsoluteLength>::zero(),
size: Size::auto(),
min_size: Size::auto(),
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::zero(),
// Aligment
align_items: None,
align_self: None,
align_content: None,
justify_content: None,
// Flexbox
flex_direction: FlexDirection::Row,
flex_wrap: FlexWrap::NoWrap,
flex_grow: 0.0,
flex_shrink: 1.0,
flex_basis: Length::Auto,
fill: None,
border_color: None,
corner_radii: CornerRadii::default(),
text_color: None,
font_size: Some(1.),
font_family: None,
font_weight: None,
font_style: None,
}
}
}
#[derive(Clone, Debug)]
pub enum Fill {
Color(Hsla),
}
impl Fill {
pub fn color(&self) -> Option<Hsla> {
match self {
Fill::Color(color) => Some(*color),
}
}
}
impl Default for Fill {
fn default() -> Self {
Self::Color(Hsla::default())
}
}
impl From<Hsla> for Fill {
fn from(color: Hsla) -> Self {
Self::Color(color)
}
}
#[derive(Clone, Refineable, Default, Debug)]
#[refineable(debug)]
pub struct CornerRadii {
top_left: AbsoluteLength,
top_right: AbsoluteLength,
bottom_left: AbsoluteLength,
bottom_right: AbsoluteLength,
}
impl CornerRadii {
pub fn to_gpui(&self, box_size: Vector2F, rem_size: f32) -> gpui::scene::CornerRadii {
let max_radius = box_size.x().min(box_size.y()) / 2.;
gpui::scene::CornerRadii {
top_left: self.top_left.to_pixels(rem_size).min(max_radius),
top_right: self.top_right.to_pixels(rem_size).min(max_radius),
bottom_left: self.bottom_left.to_pixels(rem_size).min(max_radius),
bottom_right: self.bottom_right.to_pixels(rem_size).min(max_radius),
}
}
}
pub trait Styleable {
type Style: Refineable + Default;
fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
fn computed_style(&mut self) -> Self::Style {
Self::Style::from_refinement(&self.style_cascade().merged())
}
fn hover(self) -> Hoverable<Self>
where
Self: Sized,
{
hoverable(self)
}
fn active(self) -> Pressable<Self>
where
Self: Sized,
{
pressable(self)
}
}
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
//
// Example:
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
// fn p_2(mut self) -> Self where Self: Sized;
pub trait StyleHelpers: Styleable<Style = Style> {
styleable_helpers!();
fn h(mut self, height: Length) -> Self
where
Self: Sized,
{
self.declared_style().size.height = Some(height);
self
}
/// size_{n}: Sets width & height to {n}
///
/// Example:
/// size_1: Sets width & height to 1
fn size(mut self, size: Length) -> Self
where
Self: Sized,
{
self.declared_style().size.height = Some(size);
self.declared_style().size.width = Some(size);
self
}
fn full(mut self) -> Self
where
Self: Sized,
{
self.declared_style().size.width = Some(relative(1.));
self.declared_style().size.height = Some(relative(1.));
self
}
fn relative(mut self) -> Self
where
Self: Sized,
{
self.declared_style().position = Some(Position::Relative);
self
}
fn absolute(mut self) -> Self
where
Self: Sized,
{
self.declared_style().position = Some(Position::Absolute);
self
}
fn block(mut self) -> Self
where
Self: Sized,
{
self.declared_style().display = Some(Display::Block);
self
}
fn flex(mut self) -> Self
where
Self: Sized,
{
self.declared_style().display = Some(Display::Flex);
self
}
fn flex_col(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_direction = Some(FlexDirection::Column);
self
}
fn flex_row(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_direction = Some(FlexDirection::Row);
self
}
fn flex_1(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(relative(0.));
self
}
fn flex_auto(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(Length::Auto);
self
}
fn flex_initial(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(0.);
self.declared_style().flex_shrink = Some(1.);
self.declared_style().flex_basis = Some(Length::Auto);
self
}
fn flex_none(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(0.);
self.declared_style().flex_shrink = Some(0.);
self
}
fn grow(mut self) -> Self
where
Self: Sized,
{
self.declared_style().flex_grow = Some(1.);
self
}
fn items_start(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::FlexStart);
self
}
fn items_end(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::FlexEnd);
self
}
fn items_center(mut self) -> Self
where
Self: Sized,
{
self.declared_style().align_items = Some(AlignItems::Center);
self
}
fn justify_between(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
self
}
fn justify_center(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::Center);
self
}
fn justify_start(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::Start);
self
}
fn justify_end(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::End);
self
}
fn justify_around(mut self) -> Self
where
Self: Sized,
{
self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
self
}
fn fill<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
Self: Sized,
{
self.declared_style().fill = Some(fill.into());
self
}
fn border_color<C>(mut self, border_color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().border_color = Some(border_color.into());
self
}
fn text_color<C>(mut self, color: C) -> Self
where
C: Into<Hsla>,
Self: Sized,
{
self.declared_style().text_color = Some(color.into());
self
}
fn text_xs(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(0.75);
self
}
fn text_sm(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(0.875);
self
}
fn text_base(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.0);
self
}
fn text_lg(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.125);
self
}
fn text_xl(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.25);
self
}
fn text_2xl(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.5);
self
}
fn text_3xl(mut self) -> Self
where
Self: Sized,
{
self.declared_style().font_size = Some(1.875);
self
}
fn font(mut self, family_name: impl Into<Arc<str>>) -> Self
where
Self: Sized,
{
self.declared_style().font_family = Some(family_name.into());
self
}
}

View File

@ -0,0 +1,79 @@
use std::{any::TypeId, rc::Rc};
use crate::{element::LayoutId, style::Style};
use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
#[derive(Deref, DerefMut)]
pub struct ViewContext<'a, 'b, 'c, V> {
#[deref]
#[deref_mut]
pub(crate) legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>,
}
impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
pub fn new(legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>) -> Self {
Self { legacy_cx }
}
pub fn add_layout_node(
&mut self,
style: Style,
children: impl IntoIterator<Item = NodeId>,
) -> Result<LayoutId> {
let rem_size = self.rem_size();
let style = style.to_taffy(rem_size);
let id = self
.legacy_cx
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
.add_node(style, children)?;
Ok(id)
}
pub fn add_measured_layout_node<F>(&mut self, style: Style, measure: F) -> Result<LayoutId>
where
F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
{
let rem_size = self.rem_size();
let layout_id = self
.layout_engine()
.ok_or_else(|| anyhow!("no layout engine"))?
.add_measured_node(style.to_taffy(rem_size), measure)?;
Ok(layout_id)
}
pub fn on_event<E: 'static>(
&mut self,
order: u32,
handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
) {
let view = self.weak_handle();
self.scene().event_handlers.push(EventHandler {
order,
handler: Rc::new(move |event, window_cx| {
if let Some(view) = view.upgrade(window_cx) {
view.update(window_cx, |view, view_cx| {
let mut event_cx = EventContext::new(view_cx);
handler(view, event.downcast_ref().unwrap(), &mut event_cx);
event_cx.bubble
})
} else {
true
}
}),
event_type: TypeId::of::<E>(),
})
}
pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
self.layout_engine()
.ok_or_else(|| anyhow!("no layout engine present"))?
.computed_layout(layout_id)
}
}

View File

@ -1,11 +1,11 @@
[package]
name = "playground_macros"
name = "gpui2_macros"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/playground_macros.rs"
path = "src/gpui2_macros.rs"
proc-macro = true
[dependencies]

View File

@ -59,28 +59,30 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
);
let gen = quote! {
impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics
impl #impl_generics gpui2::element::Element<#view_type_name> for #type_name #type_generics
#where_clause
{
type Layout = Option<playground::element::AnyElement<#view_type_name #lifetimes>>;
type PaintState = gpui2::element::AnyElement<#view_type_name #lifetimes>;
fn layout(
&mut self,
view: &mut V,
cx: &mut playground::element::LayoutContext<V>,
) -> anyhow::Result<playground::element::Layout<V, Self::Layout>> {
let mut element = self.render(view, cx).into_any();
let layout_id = element.layout(view, cx)?;
Ok(playground::element::Layout::new(layout_id, Some(element)))
cx: &mut gpui2::ViewContext<V>,
) -> anyhow::Result<(gpui2::element::LayoutId, Self::PaintState)> {
let mut rendered_element = self.render(view, cx).into_element().into_any();
let layout_id = rendered_element.layout(view, cx)?;
Ok((layout_id, rendered_element))
}
fn paint(
&mut self,
view: &mut V,
layout: &mut playground::element::Layout<V, Self::Layout>,
cx: &mut playground::element::PaintContext<V>,
parent_origin: gpui2::Vector2F,
_: &gpui2::element::Layout,
rendered_element: &mut Self::PaintState,
cx: &mut gpui2::ViewContext<V>,
) {
layout.paint(view, cx);
rendered_element.paint(view, parent_origin, cx);
}
}

View File

@ -56,7 +56,7 @@ pub fn impl_into_element(
where_clause: &Option<&WhereClause>,
) -> proc_macro2::TokenStream {
quote! {
impl #impl_generics playground::element::IntoElement<#view_type_name> for #type_name #type_generics
impl #impl_generics gpui2::element::IntoElement<#view_type_name> for #type_name #type_generics
#where_clause
{
type Element = Self;

View File

@ -3,7 +3,6 @@ use proc_macro::TokenStream;
mod derive_element;
mod derive_into_element;
mod styleable_helpers;
mod tailwind_lengths;
#[proc_macro]
pub fn styleable_helpers(args: TokenStream) -> TokenStream {
@ -19,8 +18,3 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
pub fn derive_into_element(input: TokenStream) -> TokenStream {
derive_into_element::derive_into_element(input)
}
#[proc_macro_attribute]
pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
tailwind_lengths::tailwind_lengths(attr, item)
}

View File

@ -0,0 +1,326 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct StyleableMacroInput;
impl Parse for StyleableMacroInput {
fn parse(_input: ParseStream) -> Result<Self> {
Ok(StyleableMacroInput)
}
}
pub fn styleable_helpers(input: TokenStream) -> TokenStream {
let _ = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_methods();
let output = quote! {
#(#methods)*
};
output.into()
}
fn generate_methods() -> Vec<TokenStream2> {
let mut methods = Vec::new();
for (prefix, auto_allowed, fields) in box_prefixes() {
for (suffix, length_tokens, doc_string) in box_suffixes() {
if auto_allowed || suffix != "auto" {
let method = generate_method(prefix, suffix, &fields, length_tokens, doc_string);
methods.push(method);
}
}
}
for (prefix, fields) in corner_prefixes() {
for (suffix, radius_tokens, doc_string) in corner_suffixes() {
let method = generate_method(prefix, suffix, &fields, radius_tokens, doc_string);
methods.push(method);
}
}
for (prefix, fields) in border_prefixes() {
for (suffix, width_tokens, doc_string) in border_suffixes() {
let method = generate_method(prefix, suffix, &fields, width_tokens, doc_string);
methods.push(method);
}
}
methods
}
fn generate_method(
prefix: &'static str,
suffix: &'static str,
fields: &Vec<TokenStream2>,
length_tokens: TokenStream2,
doc_string: &'static str,
) -> TokenStream2 {
let method_name = if suffix.is_empty() {
format_ident!("{}", prefix)
} else {
format_ident!("{}_{}", prefix, suffix)
};
let field_assignments = fields
.iter()
.map(|field_tokens| {
quote! {
style.#field_tokens = Some(gpui::geometry::#length_tokens);
}
})
.collect::<Vec<_>>();
let method = quote! {
#[doc = #doc_string]
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
let mut style = self.declared_style();
#(#field_assignments)*
self
}
};
method
}
fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
vec![
("w", true, vec![quote! { size.width }]),
("h", true, vec![quote! { size.height }]),
(
"size",
true,
vec![quote! {size.width}, quote! {size.height}],
),
("min_w", false, vec![quote! { min_size.width }]),
("min_h", false, vec![quote! { min_size.height }]),
("max_w", false, vec![quote! { max_size.width }]),
("max_h", false, vec![quote! { max_size.height }]),
(
"m",
true,
vec![
quote! { margin.top },
quote! { margin.bottom },
quote! { margin.left },
quote! { margin.right },
],
),
("mt", true, vec![quote! { margin.top }]),
("mb", true, vec![quote! { margin.bottom }]),
(
"my",
true,
vec![quote! { margin.top }, quote! { margin.bottom }],
),
(
"mx",
true,
vec![quote! { margin.left }, quote! { margin.right }],
),
("ml", true, vec![quote! { margin.left }]),
("mr", true, vec![quote! { margin.right }]),
(
"p",
false,
vec![
quote! { padding.top },
quote! { padding.bottom },
quote! { padding.left },
quote! { padding.right },
],
),
("pt", false, vec![quote! { padding.top }]),
("pb", false, vec![quote! { padding.bottom }]),
(
"px",
false,
vec![quote! { padding.left }, quote! { padding.right }],
),
(
"py",
false,
vec![quote! { padding.top }, quote! { padding.bottom }],
),
("pl", false, vec![quote! { padding.left }]),
("pr", false, vec![quote! { padding.right }]),
("top", true, vec![quote! { inset.top }]),
("bottom", true, vec![quote! { inset.bottom }]),
("left", true, vec![quote! { inset.left }]),
("right", true, vec![quote! { inset.right }]),
(
"gap",
false,
vec![quote! { gap.width }, quote! { gap.height }],
),
("gap_x", false, vec![quote! { gap.width }]),
("gap_y", false, vec![quote! { gap.height }]),
]
}
fn box_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
vec![
("0", quote! { pixels(0.) }, "0px"),
("0p5", quote! { rems(0.125) }, "2px (0.125rem)"),
("1", quote! { rems(0.25) }, "4px (0.25rem)"),
("1p5", quote! { rems(0.375) }, "6px (0.375rem)"),
("2", quote! { rems(0.5) }, "8px (0.5rem)"),
("2p5", quote! { rems(0.625) }, "10px (0.625rem)"),
("3", quote! { rems(0.75) }, "12px (0.75rem)"),
("3p5", quote! { rems(0.875) }, "14px (0.875rem)"),
("4", quote! { rems(1.) }, "16px (1rem)"),
("5", quote! { rems(1.25) }, "20px (1.25rem)"),
("6", quote! { rems(1.5) }, "24px (1.5rem)"),
("7", quote! { rems(1.75) }, "28px (1.75rem)"),
("8", quote! { rems(2.0) }, "32px (2rem)"),
("9", quote! { rems(2.25) }, "36px (2.25rem)"),
("10", quote! { rems(2.5) }, "40px (2.5rem)"),
("11", quote! { rems(2.75) }, "44px (2.75rem)"),
("12", quote! { rems(3.) }, "48px (3rem)"),
("16", quote! { rems(4.) }, "64px (4rem)"),
("20", quote! { rems(5.) }, "80px (5rem)"),
("24", quote! { rems(6.) }, "96px (6rem)"),
("32", quote! { rems(8.) }, "128px (8rem)"),
("40", quote! { rems(10.) }, "160px (10rem)"),
("48", quote! { rems(12.) }, "192px (12rem)"),
("56", quote! { rems(14.) }, "224px (14rem)"),
("64", quote! { rems(16.) }, "256px (16rem)"),
("72", quote! { rems(18.) }, "288px (18rem)"),
("80", quote! { rems(20.) }, "320px (20rem)"),
("96", quote! { rems(24.) }, "384px (24rem)"),
("auto", quote! { auto() }, "Auto"),
("px", quote! { pixels(1.) }, "1px"),
("full", quote! { relative(1.) }, "100%"),
("1_2", quote! { relative(0.5) }, "50% (1/2)"),
("1_3", quote! { relative(1./3.) }, "33% (1/3)"),
("2_3", quote! { relative(2./3.) }, "66% (2/3)"),
("1_4", quote! { relative(0.25) }, "25% (1/4)"),
("2_4", quote! { relative(0.5) }, "50% (2/4)"),
("3_4", quote! { relative(0.75) }, "75% (3/4)"),
("1_5", quote! { relative(0.2) }, "20% (1/5)"),
("2_5", quote! { relative(0.4) }, "40% (2/5)"),
("3_5", quote! { relative(0.6) }, "60% (3/5)"),
("4_5", quote! { relative(0.8) }, "80% (4/5)"),
("1_6", quote! { relative(1./6.) }, "16% (1/6)"),
("5_6", quote! { relative(5./6.) }, "80% (5/6)"),
("1_12", quote! { relative(1./12.) }, "8% (1/12)"),
]
}
fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
vec![
(
"rounded",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
quote! { corner_radii.bottom_left },
],
),
(
"rounded_t",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
],
),
(
"rounded_b",
vec![
quote! { corner_radii.bottom_left },
quote! { corner_radii.bottom_right },
],
),
(
"rounded_r",
vec![
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
],
),
(
"rounded_l",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.bottom_left },
],
),
("rounded_tl", vec![quote! { corner_radii.top_left }]),
("rounded_tr", vec![quote! { corner_radii.top_right }]),
("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
("rounded_br", vec![quote! { corner_radii.bottom_right }]),
]
}
fn corner_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
vec![
("none", quote! { pixels(0.) }, "0px"),
("sm", quote! { rems(0.125) }, "2px (0.125rem)"),
("md", quote! { rems(0.25) }, "4px (0.25rem)"),
("lg", quote! { rems(0.5) }, "8px (0.5rem)"),
("xl", quote! { rems(0.75) }, "12px (0.75rem)"),
("2xl", quote! { rems(1.) }, "16px (1rem)"),
("3xl", quote! { rems(1.5) }, "24px (1.5rem)"),
("full", quote! { pixels(9999.) }, "9999px"),
]
}
fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
vec![
(
"border",
vec![
quote! { border_widths.top },
quote! { border_widths.right },
quote! { border_widths.bottom },
quote! { border_widths.left },
],
),
("border_t", vec![quote! { border_widths.top }]),
("border_b", vec![quote! { border_widths.bottom }]),
("border_r", vec![quote! { border_widths.right }]),
("border_l", vec![quote! { border_widths.left }]),
(
"border_x",
vec![
quote! { border_widths.left },
quote! { border_widths.right },
],
),
(
"border_y",
vec![
quote! { border_widths.top },
quote! { border_widths.bottom },
],
),
]
}
fn border_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
vec![
("", quote! { pixels(1.)}, "1px"),
("0", quote! { pixels(0.)}, "0px"),
("1", quote! { pixels(1.) }, "1px"),
("2", quote! { pixels(2.) }, "2px"),
("3", quote! { pixels(3.) }, "3px"),
("4", quote! { pixels(4.) }, "4px"),
("5", quote! { pixels(5.) }, "5px"),
("6", quote! { pixels(6.) }, "6px"),
("7", quote! { pixels(7.) }, "7px"),
("8", quote! { pixels(8.) }, "8px"),
("9", quote! { pixels(9.) }, "9px"),
("10", quote! { pixels(10.) }, "10px"),
("11", quote! { pixels(11.) }, "11px"),
("12", quote! { pixels(12.) }, "12px"),
("16", quote! { pixels(16.) }, "16px"),
("20", quote! { pixels(20.) }, "20px"),
("24", quote! { pixels(24.) }, "24px"),
("32", quote! { pixels(32.) }, "32px"),
]
}

View File

@ -329,7 +329,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
&mut self,
constraint: gpui::SizeConstraint,
view: &mut V,
cx: &mut gpui::LayoutContext<V>,
cx: &mut gpui::ViewContext<V>,
) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
let mut element = self.render(view, cx).into_any();
let size = element.layout(constraint, view, cx);
@ -338,14 +338,13 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
fn paint(
&mut self,
scene: &mut gpui::SceneBuilder,
bounds: gpui::geometry::rect::RectF,
visible_bounds: gpui::geometry::rect::RectF,
element: &mut gpui::elements::AnyElement<V>,
view: &mut V,
cx: &mut gpui::PaintContext<V>,
cx: &mut gpui::ViewContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
element.paint(bounds.origin(), visible_bounds, view, cx);
}
fn rect_for_text_range(

View File

@ -13,7 +13,7 @@ use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use collections::{HashMap, HashSet};
use futures::{
channel::oneshot,
channel::{mpsc, oneshot},
future::{BoxFuture, Shared},
FutureExt, TryFutureExt as _,
};
@ -48,9 +48,6 @@ use unicase::UniCase;
use util::{http::HttpClient, paths::PathExt};
use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
#[cfg(any(test, feature = "test-support"))]
use futures::channel::mpsc;
pub use buffer::Operation;
pub use buffer::*;
pub use diagnostic_set::DiagnosticEntry;
@ -64,6 +61,27 @@ pub fn init(cx: &mut AppContext) {
language_settings::init(cx);
}
#[derive(Clone, Default)]
struct LspBinaryStatusSender {
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(Arc<Language>, LanguageServerBinaryStatus)>>>>,
}
impl LspBinaryStatusSender {
fn subscribe(&self) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
let (tx, rx) = mpsc::unbounded();
self.txs.lock().push(tx);
rx
}
fn send(&self, language: Arc<Language>, status: LanguageServerBinaryStatus) {
let mut txs = self.txs.lock();
txs.retain(|tx| {
tx.unbounded_send((language.clone(), status.clone()))
.is_ok()
});
}
}
thread_local! {
static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
}
@ -594,14 +612,13 @@ struct AvailableLanguage {
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>,
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
lsp_binary_statuses_rx: async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)>,
login_shell_env_loaded: Shared<Task<()>>,
#[allow(clippy::type_complexity)]
lsp_binary_paths: Mutex<
HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
>,
executor: Option<Arc<Background>>,
lsp_binary_status_tx: LspBinaryStatusSender,
}
struct LanguageRegistryState {
@ -624,7 +641,6 @@ pub struct PendingLanguageServer {
impl LanguageRegistry {
pub fn new(login_shell_env_loaded: Task<()>) -> Self {
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
Self {
state: RwLock::new(LanguageRegistryState {
next_language_server_id: 0,
@ -638,11 +654,10 @@ impl LanguageRegistry {
reload_count: 0,
}),
language_server_download_dir: None,
lsp_binary_statuses_tx,
lsp_binary_statuses_rx,
login_shell_env_loaded: login_shell_env_loaded.shared(),
lsp_binary_paths: Default::default(),
executor: None,
lsp_binary_status_tx: Default::default(),
}
}
@ -918,8 +933,8 @@ impl LanguageRegistry {
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
let root_path = root_path.clone();
let adapter = adapter.clone();
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
let lsp_binary_statuses = self.lsp_binary_status_tx.clone();
let task = {
let container_dir = container_dir.clone();
@ -976,8 +991,8 @@ impl LanguageRegistry {
pub fn language_server_binary_statuses(
&self,
) -> async_broadcast::Receiver<(Arc<Language>, LanguageServerBinaryStatus)> {
self.lsp_binary_statuses_rx.clone()
) -> mpsc::UnboundedReceiver<(Arc<Language>, LanguageServerBinaryStatus)> {
self.lsp_binary_status_tx.subscribe()
}
pub fn delete_server_container(
@ -1054,7 +1069,7 @@ async fn get_binary(
language: Arc<Language>,
delegate: Arc<dyn LspAdapterDelegate>,
container_dir: Arc<Path>,
statuses: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
statuses: LspBinaryStatusSender,
mut cx: AsyncAppContext,
) -> Result<LanguageServerBinary> {
if !container_dir.exists() {
@ -1081,19 +1096,15 @@ async fn get_binary(
.cached_server_binary(container_dir.to_path_buf(), delegate.as_ref())
.await
{
statuses
.broadcast((language.clone(), LanguageServerBinaryStatus::Cached))
.await?;
statuses.send(language.clone(), LanguageServerBinaryStatus::Cached);
return Ok(binary);
} else {
statuses
.broadcast((
language.clone(),
LanguageServerBinaryStatus::Failed {
error: format!("{:?}", error),
},
))
.await?;
statuses.send(
language.clone(),
LanguageServerBinaryStatus::Failed {
error: format!("{:?}", error),
},
);
}
}
@ -1105,27 +1116,21 @@ async fn fetch_latest_binary(
language: Arc<Language>,
delegate: &dyn LspAdapterDelegate,
container_dir: &Path,
lsp_binary_statuses_tx: async_broadcast::Sender<(Arc<Language>, LanguageServerBinaryStatus)>,
lsp_binary_statuses_tx: LspBinaryStatusSender,
) -> Result<LanguageServerBinary> {
let container_dir: Arc<Path> = container_dir.into();
lsp_binary_statuses_tx
.broadcast((
language.clone(),
LanguageServerBinaryStatus::CheckingForUpdate,
))
.await?;
lsp_binary_statuses_tx.send(
language.clone(),
LanguageServerBinaryStatus::CheckingForUpdate,
);
let version_info = adapter.fetch_latest_server_version(delegate).await?;
lsp_binary_statuses_tx
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloading))
.await?;
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloading);
let binary = adapter
.fetch_server_binary(version_info, container_dir.to_path_buf(), delegate)
.await?;
lsp_binary_statuses_tx
.broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded))
.await?;
lsp_binary_statuses_tx.send(language.clone(), LanguageServerBinaryStatus::Downloaded);
Ok(binary)
}

Some files were not shown because too many files have changed in this diff Show More