Compare commits

..

11 Commits

Author SHA1 Message Date
Antonio Scandurra
30239b3cc6 WIP 2024-02-26 17:53:01 +01:00
Antonio Scandurra
180421fe5a WIP 2024-02-26 15:21:43 +01:00
Antonio Scandurra
1a13995b8f Add support for group hovering, still need to wire it up 2024-02-26 13:44:56 +01:00
Antonio Scandurra
b6379a9177 Fix compile errors and render hovered quads correctly 2024-02-26 11:50:28 +01:00
Nathan Sobo
2cb041504b WIP: Start passing hover variants of quad primitives in paint_quad 2024-02-25 20:35:56 -07:00
Nathan Sobo
4d8dc79d7e Implement hover detection for scene elements
- When inserting primitives, optionally specify the hover state and whether the primitive is opaque
- After scene is completed, sort the bounds of all intersecting primitives in descending order (higher is on top)
- Walk through the intersecting bounds and consult the each intersecting primitive's metadata
    - Replace primitive with its hovered variant if present
    - Stop if the primitive was inserted with occludes_hover = true
2024-02-25 19:42:20 -07:00
Nathan Sobo
474c806331 Add BoundsTree::find_containing and try to start using it 2024-02-25 15:25:00 -07:00
Nathan Sobo
2db6ccd803 WIP: Replace primitives based on hover 2024-02-24 18:31:31 -07:00
Nathan Sobo
2d6a227258 Don't take an order in Scene::insert 2024-02-24 10:56:06 -07:00
Antonio Scandurra
a3ce933b04 Use BoundsTree in scene
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-02-23 17:51:06 +01:00
Antonio Scandurra
816c48b7d6 Introduce a BoundsTree structure
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-02-23 17:33:22 +01:00
334 changed files with 2601 additions and 1850 deletions

231
Cargo.lock generated
View File

@@ -415,9 +415,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c"
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
dependencies = [
"flate2",
"futures-core",
@@ -692,9 +692,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
[[package]]
name = "async-trait"
version = "0.1.77"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
@@ -3313,6 +3313,7 @@ dependencies = [
"fuzzy",
"gpui",
"log",
"picker",
"project",
"serde",
"serde_json",
@@ -3994,15 +3995,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.14"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex-automata 0.4.5",
"regex-syntax 0.8.2",
"regex",
]
[[package]]
@@ -4523,16 +4524,17 @@ dependencies = [
[[package]]
name = "ignore"
version = "0.4.22"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
dependencies = [
"crossbeam-deque",
"globset",
"lazy_static",
"log",
"memchr",
"regex-automata 0.4.5",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
@@ -4744,15 +4746,6 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@@ -4806,7 +4799,7 @@ dependencies = [
"schemars",
"serde",
"settings",
"shellexpand 2.1.2",
"shellexpand",
"util",
"workspace",
]
@@ -5001,89 +4994,6 @@ dependencies = [
"workspace",
]
[[package]]
name = "languages"
version = "0.1.0"
dependencies = [
"anyhow",
"async-compression",
"async-tar",
"async-trait",
"collections",
"feature_flags",
"futures 0.3.28",
"gpui",
"language",
"lazy_static",
"log",
"lsp",
"node_runtime",
"parking_lot 0.11.2",
"project",
"regex",
"rope",
"rust-embed",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"shellexpand 3.1.0",
"smol",
"task",
"text",
"theme",
"toml 0.8.10",
"tree-sitter",
"tree-sitter-astro",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-c-sharp",
"tree-sitter-clojure",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-dart",
"tree-sitter-dockerfile",
"tree-sitter-elixir",
"tree-sitter-elm",
"tree-sitter-embedded-template",
"tree-sitter-erlang",
"tree-sitter-gitcommit",
"tree-sitter-gleam",
"tree-sitter-glsl",
"tree-sitter-go",
"tree-sitter-gomod",
"tree-sitter-gowork",
"tree-sitter-haskell",
"tree-sitter-hcl",
"tree-sitter-heex",
"tree-sitter-html",
"tree-sitter-json 0.20.0",
"tree-sitter-lua",
"tree-sitter-markdown",
"tree-sitter-nix",
"tree-sitter-nu",
"tree-sitter-ocaml",
"tree-sitter-php",
"tree-sitter-prisma-io",
"tree-sitter-proto",
"tree-sitter-purescript",
"tree-sitter-python",
"tree-sitter-racket",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-scheme",
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue",
"tree-sitter-yaml",
"tree-sitter-zig",
"unindent",
"util",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -7011,7 +6921,6 @@ dependencies = [
"postage",
"prettier",
"pretty_assertions",
"project_core",
"rand 0.8.5",
"regex",
"release_channel",
@@ -7036,37 +6945,6 @@ dependencies = [
"which 6.0.0",
]
[[package]]
name = "project_core"
version = "0.1.0"
dependencies = [
"anyhow",
"client",
"clock",
"collections",
"fs",
"futures 0.3.28",
"fuzzy",
"git",
"gpui",
"ignore",
"itertools 0.12.1",
"language",
"log",
"lsp",
"parking_lot 0.11.2",
"postage",
"rpc",
"schemars",
"serde",
"serde_json",
"settings",
"smol",
"sum_tree",
"text",
"util",
]
[[package]]
name = "project_panel"
version = "0.1.0"
@@ -7461,6 +7339,7 @@ dependencies = [
name = "recent_projects"
version = "0.1.0"
dependencies = [
"editor",
"futures 0.3.28",
"fuzzy",
"gpui",
@@ -7922,9 +7801,9 @@ dependencies = [
[[package]]
name = "rust-embed"
version = "8.2.0"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -7933,9 +7812,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "8.2.0"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29"
dependencies = [
"proc-macro2",
"quote",
@@ -7946,9 +7825,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
version = "8.2.0"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada"
dependencies = [
"globset",
"sha2 0.10.7",
@@ -8667,20 +8546,11 @@ dependencies = [
"dirs 4.0.0",
]
[[package]]
name = "shellexpand"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
dependencies = [
"dirs 4.0.0",
]
[[package]]
name = "shlex"
version = "1.3.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "signal-hook"
@@ -9511,6 +9381,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"db",
"editor",
"fs",
"futures 0.3.28",
"fuzzy",
@@ -9585,7 +9456,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
"shellexpand 2.1.2",
"shellexpand",
"smallvec",
"smol",
"task",
@@ -9620,7 +9491,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
"shellexpand 2.1.2",
"shellexpand",
"smallvec",
"smol",
"task",
@@ -12135,7 +12006,6 @@ dependencies = [
"language",
"language_selector",
"language_tools",
"languages",
"lazy_static",
"libc",
"log",
@@ -12169,7 +12039,7 @@ dependencies = [
"serde_derive",
"serde_json",
"settings",
"shellexpand 2.1.2",
"shellexpand",
"simplelog",
"smallvec",
"smol",
@@ -12184,7 +12054,52 @@ dependencies = [
"thiserror",
"tiny_http",
"toml 0.8.10",
"tree-sitter",
"tree-sitter-astro",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-c-sharp",
"tree-sitter-clojure",
"tree-sitter-cpp",
"tree-sitter-css",
"tree-sitter-dart",
"tree-sitter-dockerfile",
"tree-sitter-elixir",
"tree-sitter-elm",
"tree-sitter-embedded-template",
"tree-sitter-erlang",
"tree-sitter-gitcommit",
"tree-sitter-gleam",
"tree-sitter-glsl",
"tree-sitter-go",
"tree-sitter-gomod",
"tree-sitter-gowork",
"tree-sitter-haskell",
"tree-sitter-hcl",
"tree-sitter-heex",
"tree-sitter-html",
"tree-sitter-json 0.20.0",
"tree-sitter-lua",
"tree-sitter-markdown",
"tree-sitter-nix",
"tree-sitter-nu",
"tree-sitter-ocaml",
"tree-sitter-php",
"tree-sitter-prisma-io",
"tree-sitter-proto",
"tree-sitter-purescript",
"tree-sitter-python",
"tree-sitter-racket",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-scheme",
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-uiua",
"tree-sitter-vue",
"tree-sitter-yaml",
"tree-sitter-zig",
"unindent",
"url",
"urlencoding",

View File

@@ -38,7 +38,6 @@ members = [
"crates/language",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
"crates/live_kit_client",
"crates/live_kit_server",
"crates/lsp",
@@ -54,7 +53,6 @@ members = [
"crates/plugin_macros",
"crates/prettier",
"crates/project",
"crates/project_core",
"crates/project_panel",
"crates/project_symbols",
"crates/quick_action_bar",
@@ -149,7 +147,6 @@ plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" }
project = { path = "crates/project" }
project_core = { path = "crates/project_core" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
quick_action_bar = { path = "crates/quick_action_bar" }

View File

@@ -2672,24 +2672,32 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
let right = bounds.right();
let top = bounds.top();
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
cx.paint_quad(
fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
),
),
color,
),
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
None,
None,
);
cx.paint_quad(
fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
),
None,
None,
);
})
.w(width)
.h(line_height)

View File

@@ -15,6 +15,7 @@ client.workspace = true
collections.workspace = true
# HACK: We're only depending on `copilot` here for `CommandPaletteFilter`. See the attached comment on that type.
copilot.workspace = true
editor.workspace = true
fuzzy.workspace = true
gpui.workspace = true
picker.workspace = true

View File

@@ -1584,6 +1584,7 @@ mod tests {
}
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
let editor_view = editor.clone();
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
snapshot
@@ -1592,6 +1593,7 @@ mod tests {
.filter_map(|(ix, (row, block))| {
let name: SharedString = match block {
TransformBlock::Custom(block) => cx.with_element_context({
let editor_view = editor_view.clone();
|cx| -> Option<SharedString> {
block
.render(&mut BlockContext {
@@ -1602,6 +1604,7 @@ mod tests {
em_width: px(0.),
max_width: px(0.),
block_id: ix,
view: editor_view,
editor_style: &editor::EditorStyle::default(),
})
.inner_id()?

View File

@@ -24,7 +24,10 @@ mod tab_map;
mod wrap_map;
use crate::EditorStyle;
use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
use crate::{
hover_links::InlayHighlight, movement::TextLayoutDetails, Anchor, AnchorRangeExt, InlayId,
MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
};
pub use block_map::{BlockMap, BlockPoint};
use collections::{BTreeMap, HashMap, HashSet};
use fold_map::FoldMap;
@@ -34,7 +37,6 @@ use language::{
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
use sum_tree::{Bias, TreeMap};
use tab_map::TabMap;

View File

@@ -2,11 +2,10 @@ use super::{
wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
Highlights,
};
use crate::{EditorStyle, GutterDimensions};
use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, ToPoint as _};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, ElementContext, Pixels};
use gpui::{AnyElement, ElementContext, Pixels, View};
use language::{BufferSnapshot, Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
use parking_lot::Mutex;
use std::{
cell::RefCell,
@@ -86,6 +85,7 @@ pub enum BlockStyle {
pub struct BlockContext<'a, 'b> {
pub context: &'b mut ElementContext<'a>,
pub view: View<Editor>,
pub anchor_x: Pixels,
pub max_width: Pixels,
pub gutter_dimensions: &'b GutterDimensions,

View File

@@ -2,9 +2,9 @@ use super::{
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
Highlights,
};
use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
use gpui::{ElementId, HighlightStyle, Hsla};
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset};
use std::{
any::TypeId,
cmp::{self, Ordering},

View File

@@ -1,8 +1,8 @@
use crate::InlayId;
use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset};
use collections::{BTreeMap, BTreeSet};
use gpui::HighlightStyle;
use language::{Chunk, Edit, Point, TextSummary};
use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferRows, MultiBufferSnapshot, ToOffset};
use multi_buffer::{MultiBufferChunks, MultiBufferRows};
use std::{
any::TypeId,
cmp,

View File

@@ -2,8 +2,8 @@ use super::{
fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
Highlights,
};
use crate::MultiBufferSnapshot;
use language::{Chunk, Point};
use multi_buffer::MultiBufferSnapshot;
use std::{cmp, mem, num::NonZeroU32, ops::Range};
use sum_tree::Bias;

View File

@@ -3,10 +3,10 @@ use super::{
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
Highlights,
};
use crate::MultiBufferSnapshot;
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
use language::{Chunk, Point};
use lazy_static::lazy_static;
use multi_buffer::MultiBufferSnapshot;
use smol::future::yield_now;
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
use sum_tree::{Bias, Cursor, SumTree};

View File

@@ -122,7 +122,7 @@ use ui::{
};
use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::Toast;
use workspace::{searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace};
use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace};
use crate::hover_links::find_url;
@@ -356,6 +356,7 @@ type InlayBackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<InlayHighlight>);
///
/// See the [module level documentation](self) for more information.
pub struct Editor {
handle: WeakView<Self>,
focus_handle: FocusHandle,
/// The text buffer being edited
buffer: Model<MultiBuffer>,
@@ -1487,6 +1488,7 @@ impl Editor {
cx.on_blur(&focus_handle, Self::handle_blur).detach();
let mut this = Self {
handle: cx.view().downgrade(),
focus_handle,
buffer: buffer.clone(),
display_map: display_map.clone(),
@@ -1684,6 +1686,10 @@ impl Editor {
self.workspace.as_ref()?.0.upgrade()
}
pub fn pane(&self, cx: &AppContext) -> Option<View<Pane>> {
self.workspace()?.read(cx).pane_for(&self.handle.upgrade()?)
}
pub fn title<'a>(&self, cx: &'a AppContext) -> Cow<'a, str> {
self.buffer().read(cx).title(cx)
}

View File

@@ -632,8 +632,8 @@ impl EditorElement {
let scroll_top =
layout.position_map.snapshot.scroll_position().y * layout.position_map.line_height;
let gutter_bg = cx.theme().colors().editor_gutter_background;
cx.paint_quad(fill(gutter_bounds, gutter_bg));
cx.paint_quad(fill(text_bounds, self.style.background));
cx.paint_quad(fill(gutter_bounds, gutter_bg), None, None);
cx.paint_quad(fill(text_bounds, self.style.background), None, None);
if let EditorMode::Full = layout.mode {
let mut active_rows = layout.active_rows.iter().peekable();
@@ -657,7 +657,7 @@ impl EditorElement {
layout.position_map.line_height * (end_row - start_row + 1) as f32,
);
let active_line_bg = cx.theme().colors().editor_active_line_background;
cx.paint_quad(fill(Bounds { origin, size }, active_line_bg));
cx.paint_quad(fill(Bounds { origin, size }, active_line_bg), None, None);
}
}
@@ -673,7 +673,11 @@ impl EditorElement {
layout.position_map.line_height * highlighted_rows.len() as f32,
);
let highlighted_line_bg = cx.theme().colors().editor_highlighted_line_background;
cx.paint_quad(fill(Bounds { origin, size }, highlighted_line_bg));
cx.paint_quad(
fill(Bounds { origin, size }, highlighted_line_bg),
None,
None,
);
}
let scroll_left =
@@ -694,13 +698,17 @@ impl EditorElement {
} else {
cx.theme().colors().editor_wrap_guide
};
cx.paint_quad(fill(
Bounds {
origin: point(x, text_bounds.origin.y),
size: size(px(1.), text_bounds.size.height),
},
color,
));
cx.paint_quad(
fill(
Bounds {
origin: point(x, text_bounds.origin.y),
size: size(px(1.), text_bounds.size.height),
},
color,
),
None,
None,
);
}
}
}
@@ -804,13 +812,17 @@ impl EditorElement {
let highlight_origin = bounds.origin + point(-width, start_y);
let highlight_size = size(width * 2., end_y - start_y);
let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
cx.paint_quad(quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().modified,
Edges::default(),
transparent_black(),
));
cx.paint_quad(
quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().modified,
Edges::default(),
transparent_black(),
),
None,
None,
);
continue;
}
@@ -837,13 +849,17 @@ impl EditorElement {
let highlight_origin = bounds.origin + point(-width, start_y);
let highlight_size = size(width * 2., end_y - start_y);
let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
cx.paint_quad(quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().deleted,
Edges::default(),
transparent_black(),
));
cx.paint_quad(
quad(
highlight_bounds,
Corners::all(1. * line_height),
cx.theme().status().deleted,
Edges::default(),
transparent_black(),
),
None,
None,
);
continue;
}
@@ -877,13 +893,17 @@ impl EditorElement {
let highlight_origin = bounds.origin + point(-width, start_y);
let highlight_size = size(width * 2., end_y - start_y);
let highlight_bounds = Bounds::new(highlight_origin, highlight_size);
cx.paint_quad(quad(
highlight_bounds,
Corners::all(0.05 * line_height),
color,
Edges::default(),
transparent_black(),
));
cx.paint_quad(
quad(
highlight_bounds,
Corners::all(0.05 * line_height),
color,
Edges::default(),
transparent_black(),
),
None,
None,
);
}
}
@@ -1346,18 +1366,22 @@ impl EditorElement {
let thumb_bounds = Bounds::from_corners(point(left, thumb_top), point(right, thumb_bottom));
if layout.show_scrollbars {
cx.paint_quad(quad(
track_bounds,
Corners::default(),
cx.theme().colors().scrollbar_track_background,
Edges {
top: Pixels::ZERO,
right: Pixels::ZERO,
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_track_border,
));
cx.paint_quad(
quad(
track_bounds,
Corners::default(),
cx.theme().colors().scrollbar_track_background,
Edges {
top: Pixels::ZERO,
right: Pixels::ZERO,
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_track_border,
),
None,
None,
);
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
if layout.is_singleton && scrollbar_settings.selections {
let start_anchor = Anchor::min();
@@ -1377,18 +1401,22 @@ impl EditorElement {
end_y = start_y + px(1.);
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
cx.paint_quad(quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
cx.paint_quad(
quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
),
None,
None,
);
}
}
@@ -1415,18 +1443,22 @@ impl EditorElement {
}
let bounds = Bounds::from_corners(point(left, start_y), point(right, end_y));
cx.paint_quad(quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
cx.paint_quad(
quad(
bounds,
Corners::default(),
cx.theme().status().info,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
),
None,
None,
);
}
}
@@ -1458,18 +1490,22 @@ impl EditorElement {
DiffHunkStatus::Modified => cx.theme().status().modified,
DiffHunkStatus::Removed => cx.theme().status().deleted,
};
cx.paint_quad(quad(
bounds,
Corners::default(),
color,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
cx.paint_quad(
quad(
bounds,
Corners::default(),
color,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
),
None,
None,
);
}
}
@@ -1516,33 +1552,41 @@ impl EditorElement {
DiagnosticSeverity::INFORMATION => cx.theme().status().info,
_ => cx.theme().status().hint,
};
cx.paint_quad(quad(
bounds,
Corners::default(),
color,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
cx.paint_quad(
quad(
bounds,
Corners::default(),
color,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
),
None,
None,
);
}
}
cx.paint_quad(quad(
thumb_bounds,
Corners::default(),
cx.theme().colors().scrollbar_thumb_background,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
));
cx.paint_quad(
quad(
thumb_bounds,
Corners::default(),
cx.theme().colors().scrollbar_thumb_background,
Edges {
top: Pixels::ZERO,
right: px(1.),
bottom: Pixels::ZERO,
left: px(1.),
},
cx.theme().colors().scrollbar_thumb_border,
),
None,
None,
);
}
let interactive_track_bounds = InteractiveBounds {
@@ -2225,6 +2269,7 @@ impl EditorElement {
.width;
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
let editor_view = cx.view().clone();
let (scroll_width, blocks) = cx.with_element_context(|cx| {
cx.with_element_id(Some("editor_blocks"), |cx| {
self.layout_blocks(
@@ -2240,6 +2285,7 @@ impl EditorElement {
&style,
&line_layouts,
editor,
editor_view,
cx,
)
})
@@ -2434,6 +2480,7 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[LineWithInvisibles],
editor: &mut Editor,
editor_view: View<Editor>,
cx: &mut ElementContext,
) -> (Pixels, Vec<BlockLayout>) {
let mut block_id = 0;
@@ -2474,6 +2521,7 @@ impl EditorElement {
em_width,
block_id,
max_width: scroll_width.max(text_width),
view: editor_view.clone(),
editor_style: &self.style,
})
}
@@ -3417,7 +3465,7 @@ impl Cursor {
})
}
cx.paint_quad(cursor);
cx.paint_quad(cursor, None, None);
if let Some(block_text) = &self.block_text {
block_text

View File

@@ -24,6 +24,7 @@ futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
log.workspace = true
picker.workspace = true
project.workspace = true
serde.workspace = true
serde_json.workspace = true

View File

@@ -94,7 +94,7 @@ fn generate_shader_bindings() -> PathBuf {
let mut builder = cbindgen::Builder::new();
let src_paths = [
crate_dir.join("src/scene.rs"),
crate_dir.join("src/scene/primitives.rs"),
crate_dir.join("src/geometry.rs"),
crate_dir.join("src/color.rs"),
crate_dir.join("src/window.rs"),

View File

@@ -0,0 +1,363 @@
use crate::{Bounds, Half, Point};
use std::{
cmp,
fmt::Debug,
ops::{Add, Sub},
};
#[derive(Debug)]
pub struct BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
root: Option<usize>,
nodes: Vec<Node<U, T>>,
stack: Vec<usize>,
}
impl<U, T> BoundsTree<U, T>
where
U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
T: Clone + Debug,
{
pub fn clear(&mut self) {
self.root = None;
self.nodes.clear();
self.stack.clear();
}
pub fn insert(&mut self, new_bounds: Bounds<U>, payload: T) -> u32 {
// If the tree is empty, make the root the new leaf.
if self.root.is_none() {
let new_node = self.push_leaf(new_bounds, payload, 1);
self.root = Some(new_node);
return 1;
}
// Search for the best place to add the new leaf based on heuristics.
let mut max_intersecting_ordering = 0;
let mut index = self.root.unwrap();
while let Node::Internal {
left,
right,
bounds: node_bounds,
..
} = &mut self.nodes[index]
{
let left = *left;
let right = *right;
*node_bounds = node_bounds.union(&new_bounds);
self.stack.push(index);
// Descend to the best-fit child, based on which one would increase
// the surface area the least. This attempts to keep the tree balanced
// in terms of surface area. If there is an intersection with the other child,
// add its keys to the intersections vector.
let left_cost = new_bounds
.union(&self.nodes[left].bounds())
.half_perimeter();
let right_cost = new_bounds
.union(&self.nodes[right].bounds())
.half_perimeter();
if left_cost < right_cost {
max_intersecting_ordering =
self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
index = left;
} else {
max_intersecting_ordering =
self.find_max_ordering(left, &new_bounds, max_intersecting_ordering);
index = right;
}
}
// We've found a leaf ('index' now refers to a leaf node).
// We'll insert a new parent node above the leaf and attach our new leaf to it.
let sibling = index;
// Check for collision with the located leaf node
let Node::Leaf {
bounds: sibling_bounds,
order: sibling_ordering,
..
} = &self.nodes[index]
else {
unreachable!();
};
if sibling_bounds.intersects(&new_bounds) {
max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering);
}
let ordering = max_intersecting_ordering + 1;
let new_node = self.push_leaf(new_bounds, payload, ordering);
let new_parent = self.push_internal(sibling, new_node);
// If there was an old parent, we need to update its children indices.
if let Some(old_parent) = self.stack.last().copied() {
let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else {
unreachable!();
};
if *left == sibling {
*left = new_parent;
} else {
*right = new_parent;
}
} else {
// If the old parent was the root, the new parent is the new root.
self.root = Some(new_parent);
}
for node_index in self.stack.drain(..) {
let Node::Internal {
max_order: max_ordering,
..
} = &mut self.nodes[node_index]
else {
unreachable!()
};
*max_ordering = cmp::max(*max_ordering, ordering);
}
ordering
}
/// Finds all nodes whose bounds contain the given point and pushes their (bounds, payload) pairs onto the result vector.
pub(crate) fn find_containing(
&mut self,
point: &Point<U>,
result: &mut Vec<BoundsSearchResult<U, T>>,
) {
if let Some(mut index) = self.root {
self.stack.clear();
self.stack.push(index);
while let Some(current_index) = self.stack.pop() {
match &self.nodes[current_index] {
Node::Leaf {
bounds,
order,
data,
} => {
if bounds.contains(point) {
result.push(BoundsSearchResult {
bounds: bounds.clone(),
order: *order,
data: data.clone(),
});
}
}
Node::Internal {
left,
right,
bounds,
..
} => {
if bounds.contains(point) {
self.stack.push(*left);
self.stack.push(*right);
}
}
}
}
}
}
fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
match {
let this = &self;
&this.nodes[index]
} {
Node::Leaf {
bounds: node_bounds,
order: ordering,
..
} => {
if bounds.intersects(node_bounds) {
max_ordering = cmp::max(*ordering, max_ordering);
}
}
Node::Internal {
left,
right,
bounds: node_bounds,
max_order: node_max_ordering,
..
} => {
if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering {
let left_max_ordering = self.nodes[*left].max_ordering();
let right_max_ordering = self.nodes[*right].max_ordering();
if left_max_ordering > right_max_ordering {
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
} else {
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
}
}
}
}
max_ordering
}
fn push_leaf(&mut self, bounds: Bounds<U>, payload: T, order: u32) -> usize {
self.nodes.push(Node::Leaf {
bounds,
data: payload,
order,
});
self.nodes.len() - 1
}
fn push_internal(&mut self, left: usize, right: usize) -> usize {
let left_node = &self.nodes[left];
let right_node = &self.nodes[right];
let new_bounds = left_node.bounds().union(right_node.bounds());
let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering());
self.nodes.push(Node::Internal {
bounds: new_bounds,
left,
right,
max_order: max_ordering,
});
self.nodes.len() - 1
}
}
impl<U, T> Default for BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
fn default() -> Self {
BoundsTree {
root: None,
nodes: Vec::new(),
stack: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
enum Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + Debug,
{
Leaf {
bounds: Bounds<U>,
order: u32,
data: T,
},
Internal {
left: usize,
right: usize,
bounds: Bounds<U>,
max_order: u32,
},
}
impl<U, T> Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + Debug,
{
fn bounds(&self) -> &Bounds<U> {
match self {
Node::Leaf { bounds, .. } => bounds,
Node::Internal { bounds, .. } => bounds,
}
}
fn max_ordering(&self) -> u32 {
match self {
Node::Leaf {
order: ordering, ..
} => *ordering,
Node::Internal {
max_order: max_ordering,
..
} => *max_ordering,
}
}
}
pub struct BoundsSearchResult<U: Clone + Default + Debug, T> {
pub bounds: Bounds<U>,
pub order: u32,
pub data: T,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Bounds, Point, Size};
#[test]
fn test_insert_and_find_containing() {
let mut tree = BoundsTree::<f32, String>::default();
let bounds1 = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
let bounds2 = Bounds {
origin: Point { x: 5.0, y: 5.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
let bounds3 = Bounds {
origin: Point { x: 10.0, y: 10.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
// Insert bounds into the tree
tree.insert(bounds1.clone(), "Payload 1".to_string());
tree.insert(bounds2.clone(), "Payload 2".to_string());
tree.insert(bounds3.clone(), "Payload 3".to_string());
// Points for testing
let point_inside_bounds1 = Point { x: 1.0, y: 1.0 };
let point_inside_bounds1_and_2 = Point { x: 6.0, y: 6.0 };
let point_inside_bounds2_and_3 = Point { x: 12.0, y: 12.0 };
let point_outside_all_bounds = Point { x: 21.0, y: 21.0 };
assert!(!bounds1.contains(&point_inside_bounds2_and_3));
assert!(!bounds1.contains(&point_outside_all_bounds));
assert!(bounds2.contains(&point_inside_bounds1_and_2));
assert!(bounds2.contains(&point_inside_bounds2_and_3));
assert!(!bounds2.contains(&point_outside_all_bounds));
assert!(!bounds3.contains(&point_inside_bounds1));
assert!(bounds3.contains(&point_inside_bounds2_and_3));
assert!(!bounds3.contains(&point_outside_all_bounds));
// Test find_containing for different points
let mut result = Vec::new();
tree.find_containing(&point_inside_bounds1, &mut result);
assert_eq!(result.len(), 1);
assert_eq!(result[0].data, "Payload 1");
result.clear();
tree.find_containing(&point_inside_bounds1_and_2, &mut result);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|r| r.data == "Payload 1"));
assert!(result.iter().any(|r| r.data == "Payload 2"));
result.clear();
tree.find_containing(&point_inside_bounds2_and_3, &mut result);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|r| r.data == "Payload 2"));
assert!(result.iter().any(|r| r.data == "Payload 3"));
result.clear();
tree.find_containing(&point_outside_all_bounds, &mut result);
assert_eq!(result.len(), 0);
}
}

View File

@@ -338,6 +338,11 @@ impl Hsla {
self.a == 0.0
}
/// Returns true if the HSLA color is fully opaque, false otherwise.
pub fn is_opaque(&self) -> bool {
self.a == 1.0
}
/// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
///
/// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.

View File

@@ -45,7 +45,7 @@ impl Element for Canvas {
}
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
style.paint(bounds, cx, |cx| {
style.paint(bounds, None, None, cx, |cx| {
(self.paint_callback.take().unwrap())(&bounds, cx)
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -152,7 +152,7 @@ impl Element for Img {
let size = size(surface.width().into(), surface.height().into());
let new_bounds = preserve_aspect_ratio(bounds, size);
// TODO: Add support for corner_radii and grayscale.
cx.paint_surface(new_bounds, surface);
cx.paint_surface(new_bounds, surface, true);
}
};
});

View File

@@ -828,6 +828,28 @@ where
y: self.origin.y.clone() + self.size.height.clone().half(),
}
}
/// Calculates the half perimeter of a rectangle defined by the bounds.
///
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
/// to sum the width and height for the half perimeter.
///
/// # Examples
///
/// ```
/// # use zed::{Bounds, Point, Size};
/// let bounds = Bounds {
/// origin: Point { x: 0, y: 0 },
/// size: Size { width: 10, height: 20 },
/// };
/// let half_perimeter = bounds.half_perimeter();
/// assert_eq!(half_perimeter, 30);
/// ```
pub fn half_perimeter(&self) -> T {
self.size.width.clone() + self.size.height.clone()
}
}
impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
@@ -2617,6 +2639,12 @@ pub trait Half {
fn half(&self) -> Self;
}
impl Half for i32 {
fn half(&self) -> Self {
self / 2
}
}
impl Half for f32 {
fn half(&self) -> Self {
self / 2.

View File

@@ -70,6 +70,7 @@ mod app;
mod arena;
mod assets;
mod bounds_tree;
mod color;
mod element;
mod elements;
@@ -117,6 +118,7 @@ pub use anyhow::Result;
pub use app::*;
pub(crate) use arena::*;
pub use assets::*;
pub(crate) use bounds_tree::*;
pub use color::*;
pub use ctor::ctor;
pub use element::*;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,345 @@
use crate::{
point, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, Point,
ScaledPixels,
};
use std::fmt::Debug;
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Quad {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub background: Hsla,
pub border_color: Hsla,
pub corner_radii: Corners<ScaledPixels>,
pub border_widths: Edges<ScaledPixels>,
}
impl Ord for Quad {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Quad {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Underline {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub thickness: ScaledPixels,
pub wavy: bool,
}
impl Ord for Underline {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Underline {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Shadow {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub blur_radius: ScaledPixels,
pub pad: u32, // align to 8 bytes
}
impl Ord for Shadow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Shadow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
}
impl Ord for MonochromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for MonochromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
pub grayscale: bool,
pub pad: u32, // align to 8 bytes
}
impl Ord for PolychromeSprite {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.order.cmp(&other.order) {
std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
order => order,
}
}
}
impl PartialOrd for PolychromeSprite {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Surface {
pub view_id: ViewId,
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
#[cfg(target_os = "macos")]
pub image_buffer: media::core_video::CVImageBuffer,
}
impl Ord for Surface {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Surface {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize);
/// A line made up of a series of vertices and control points.
#[derive(Debug)]
pub struct Path<P: Clone + Default + Debug> {
pub(crate) id: PathId,
pub(crate) view_id: ViewId,
pub(crate) order: u32,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
pub(crate) color: Hsla,
pub(crate) start: Point<P>,
pub(crate) current: Point<P>,
pub(crate) contour_count: usize,
}
impl Path<Pixels> {
/// Create a new path with the given starting point.
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
view_id: ViewId::default(),
order: u32::default(),
vertices: Vec::new(),
start,
current: start,
bounds: Bounds {
origin: start,
size: Default::default(),
},
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
}
}
/// Scale this path by the given factor.
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
view_id: self.view_id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
.map(|vertex| vertex.scale(factor))
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
}
}
/// Draw a straight line from the current point to the given point.
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.current = to;
}
/// Draw a curve from the current point to the given point, using the given control point.
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.push_triangle(
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
self.current = to;
}
fn push_triangle(
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
self.bounds = self
.bounds
.union(&Bounds {
origin: xy.0,
size: Default::default(),
})
.union(&Bounds {
origin: xy.1,
size: Default::default(),
})
.union(&Bounds {
origin: xy.2,
size: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.0,
st_position: st.0,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.1,
st_position: st.1,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.2,
st_position: st.2,
content_mask: Default::default(),
});
}
}
impl Eq for Path<ScaledPixels> {}
impl PartialEq for Path<ScaledPixels> {
fn eq(&self, other: &Self) -> bool {
self.order == other.order
}
}
impl Ord for Path<ScaledPixels> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Path<ScaledPixels> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct PathVertex<P: Clone + Default + Debug> {
pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
}
impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {
xy_position: self.xy_position.scale(factor),
st_position: self.st_position,
content_mask: self.content_mask.scale(factor),
}
}
}
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct ViewId {
low_bits: u32,
high_bits: u32,
}
impl From<EntityId> for ViewId {
fn from(value: EntityId) -> Self {
let value = value.as_u64();
Self {
low_bits: value as u32,
high_bits: (value >> 32) as u32,
}
}
}
impl From<ViewId> for EntityId {
fn from(value: ViewId) -> Self {
let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32);
value.into()
}
}

View File

@@ -383,10 +383,12 @@ impl Style {
}
}
/// Paints the background of an element styled with this style.
/// Paints the background of an element styled with this style, then calls the continuation function, then paints the border.
pub fn paint(
&self,
bounds: Bounds<Pixels>,
hover: Option<Self>,
group_hover: Option<(SharedString, Option<Self>)>,
cx: &mut ElementContext,
continuation: impl FnOnce(&mut ElementContext),
) {
@@ -397,7 +399,7 @@ impl Style {
#[cfg(debug_assertions)]
if self.debug || cx.has_global::<DebugBelow>() {
cx.paint_quad(crate::outline(bounds, crate::red()));
cx.paint_quad(crate::outline(bounds, crate::red()), None, None);
}
let rem_size = cx.rem_size();
@@ -410,101 +412,229 @@ impl Style {
);
});
let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.map_or(false, |color| !color.is_transparent()) {
cx.with_z_index(1, |cx| {
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
});
}
let named_hover_group = group_hover
.as_ref()
.map(|(group_name, _)| group_name.clone());
cx.with_hover_group(named_hover_group, |cx| {
let background_color = self.background_color();
let hover_background_color = hover
.as_ref()
.map(|hover_style| hover_style.background_color())
.unwrap_or_default();
let group_hover_background_color = group_hover
.as_ref()
.and_then(|(_, group_hover_style)| {
Some(group_hover_style.as_ref()?.background_color())
})
.unwrap_or_default();
cx.with_z_index(2, |cx| {
continuation(cx);
});
if !background_color.is_transparent()
|| !hover_background_color.is_transparent()
|| !group_hover_background_color.is_transparent()
{
cx.with_z_index(1, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
if self.is_border_visible() {
cx.with_z_index(3, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let mut border_color = background_color;
border_color.a = 0.;
let base_quad = quad(
bounds,
corner_radii,
background_color,
Edges::default(),
border_color,
);
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right()
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left()
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let hover_quad = if hover_background_color.is_transparent() {
None
} else {
let mut border_color = hover_background_color;
border_color.a = 0.;
Some(quad(
bounds,
corner_radii,
hover_background_color,
Edges::default(),
border_color,
))
};
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
let group_hover_quad = group_hover.as_ref().map(|(group_id, _)| {
let quad = if group_hover_background_color.is_transparent() {
None
} else {
let mut border_color = group_hover_background_color;
border_color.a = 0.;
Some(quad(
bounds,
corner_radii,
group_hover_background_color,
Edges::default(),
border_color,
))
};
(group_id.clone(), quad)
});
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
cx.paint_quad(base_quad, hover_quad, group_hover_quad);
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
});
}
}
#[cfg(debug_assertions)]
if self.debug_below {
cx.remove_global::<DebugBelow>();
cx.with_z_index(2, |cx| {
continuation(cx);
});
let border_color = self.border_color();
let hover_border_color = hover
.as_ref()
.map(|hover_style| hover_style.border_color())
.unwrap_or_default();
let group_hover_border_color = group_hover
.as_ref()
.and_then(|(_, group_hover_style)| Some(group_hover_style.as_ref()?.border_color()))
.unwrap_or_default();
if self.border_widths.any(|width| !width.is_zero())
&& (!border_color.is_transparent()
|| !hover_border_color.is_transparent()
|| !group_hover_border_color.is_transparent())
{
cx.with_z_index(3, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right()
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left()
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let mut background = border_color;
background.a = 0.;
let border_quad = quad(
bounds,
corner_radii,
background,
border_widths,
border_color,
);
let hover_border_quad = if hover_border_color.is_transparent() {
None
} else {
let mut background = hover_border_color;
background.a = 0.;
Some(quad(
bounds,
corner_radii,
background,
border_widths,
hover_border_color,
))
};
let group_hover_border_quad = group_hover.as_ref().map(|(group_id, _)| {
let quad = if group_hover_border_color.is_transparent() {
None
} else {
let mut background = group_hover_border_color;
background.a = 0.;
Some(quad(
bounds,
corner_radii,
background,
border_widths,
group_hover_border_color,
))
};
(group_id.clone(), quad)
});
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(
border_quad.clone(),
hover_border_quad.clone(),
group_hover_border_quad.clone(),
);
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(
border_quad.clone(),
hover_border_quad.clone(),
group_hover_border_quad.clone(),
);
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(
border_quad.clone(),
hover_border_quad.clone(),
group_hover_border_quad.clone(),
);
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(border_quad, hover_border_quad, group_hover_border_quad);
},
);
});
}
#[cfg(debug_assertions)]
if self.debug_below {
cx.remove_global::<DebugBelow>();
}
})
}
/// Returns the background color of the style based on the visibility.
/// If the visibility is `Visible`, it returns the background color of the style if set,
/// otherwise it returns the default color. If the visibility is `Hidden`, it returns
/// a transparent black color.
fn background_color(&self) -> Hsla {
match self.visibility {
Visibility::Visible => self
.background
.as_ref()
.and_then(Fill::color)
.unwrap_or_default(),
Visibility::Hidden => Hsla::transparent_black(),
}
}
fn is_border_visible(&self) -> bool {
self.border_color
.map_or(false, |color| !color.is_transparent())
&& self.border_widths.any(|length| !length.is_zero())
/// Returns the border color of the style based on the visibility.
/// If the visibility is `Visible`, it returns the border color of the style if set,
/// otherwise it returns the default color. If the visibility is `Hidden`, it returns
/// a transparent black color.
fn border_color(&self) -> Hsla {
match self.visibility {
Visibility::Visible => self.border_color.unwrap_or_default(),
Visibility::Hidden => Hsla::transparent_black(),
}
}
}

View File

@@ -130,13 +130,17 @@ fn paint_line(
if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
wraps.next();
if let Some((background_origin, background_color)) = current_background.as_mut() {
cx.paint_quad(fill(
Bounds {
origin: *background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
*background_color,
));
cx.paint_quad(
fill(
Bounds {
origin: *background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
*background_color,
),
None,
None,
);
background_origin.x = origin.x;
background_origin.y += line_height;
}
@@ -229,13 +233,17 @@ fn paint_line(
}
if let Some((background_origin, background_color)) = finished_background {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
background_color,
));
cx.paint_quad(
fill(
Bounds {
origin: background_origin,
size: size(glyph_origin.x - background_origin.x, line_height),
},
background_color,
),
None,
None,
);
}
if let Some((underline_origin, underline_style)) = finished_underline {
@@ -289,13 +297,17 @@ fn paint_line(
}
if let Some((background_origin, background_color)) = current_background.take() {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(last_line_end_x - background_origin.x, line_height),
},
background_color,
));
cx.paint_quad(
fill(
Bounds {
origin: background_origin,
size: size(last_line_end_x - background_origin.x, line_height),
},
background_color,
),
None,
None,
);
}
if let Some((underline_start, underline_style)) = current_underline.take() {

View File

@@ -212,7 +212,8 @@ impl AnyView {
/// When using this method, the view's previous layout and paint will be recycled from the previous frame if [ViewContext::notify] has not been called since it was rendered.
/// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored.
pub fn cached(mut self) -> Self {
self.cache = true;
// TODO!: ENABLE ME!
// self.cache = true;
self
}

View File

@@ -5,8 +5,8 @@ use crate::{
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
PromptLevel, Quad, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, ViewId, VisualContext, WeakView, WindowAppearance, WindowBounds,
WindowOptions, WindowTextSystem,
};
use anyhow::{anyhow, Context as _, Result};
@@ -1043,9 +1043,10 @@ impl<'a> WindowContext<'a> {
self.window.layout_engine.as_mut().unwrap().clear();
self.text_system()
.finish_frame(&self.window.next_frame.reused_views);
let mouse_position = self.window.mouse_position.scale(self.window.scale_factor);
self.window
.next_frame
.finish(&mut self.window.rendered_frame);
.finish(&mut self.window.rendered_frame, mouse_position);
ELEMENT_ARENA.with_borrow_mut(|element_arena| {
let percentage = (element_arena.len() as f32 / element_arena.capacity() as f32) * 100.;
if percentage >= 80. {
@@ -2797,6 +2798,24 @@ impl PaintQuad {
..self
}
}
pub(crate) fn into_primitive(
self,
view_id: impl Into<ViewId>,
scale_factor: f32,
content_mask: ContentMask<Pixels>,
) -> Quad {
Quad {
view_id: view_id.into(),
order: 0,
bounds: self.bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
background: self.background,
border_color: self.border_color,
corner_radii: self.corner_radii.scale(scale_factor),
border_widths: self.border_widths.scale(scale_factor),
}
}
}
/// Creates a quad with the given parameters.

View File

@@ -34,9 +34,9 @@ use crate::{
EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad,
Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext,
StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle,
Window, WindowContext, SUBPIXEL_VARIANTS,
RenderImageParams, RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size,
StackingContext, StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline,
UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS,
};
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
@@ -124,7 +124,7 @@ impl Frame {
.unwrap_or_default()
}
pub(crate) fn finish(&mut self, prev_frame: &mut Self) {
pub(crate) fn finish(&mut self, prev_frame: &mut Self, mouse_position: Point<ScaledPixels>) {
// Reuse mouse listeners that didn't change since the last frame.
for (type_id, listeners) in &mut prev_frame.mouse_listeners {
let next_listeners = self.mouse_listeners.entry(*type_id).or_default();
@@ -157,7 +157,7 @@ impl Frame {
// Reuse geometry that didn't change since the last frame.
self.scene
.reuse_views(&self.reused_views, &mut prev_frame.scene);
self.scene.finish();
self.scene.finish(mouse_position);
}
}
@@ -651,6 +651,7 @@ impl<'a> ElementContext<'a> {
}
})
}
/// Paint one or more drop shadows into the scene for the next frame at the current z-index.
pub fn paint_shadows(
&mut self,
@@ -666,11 +667,9 @@ impl<'a> ElementContext<'a> {
let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
shadow_bounds.dilate(shadow.spread_radius);
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_shadow(
Shadow {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: shadow_bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -679,6 +678,8 @@ impl<'a> ElementContext<'a> {
blur_radius: shadow.blur_radius.scale(scale_factor),
pad: 0,
},
None,
None,
);
}
}
@@ -686,17 +687,20 @@ impl<'a> ElementContext<'a> {
/// Paint one or more quads into the scene for the next frame at the current stacking context.
/// Quads are colored rectangular regions with an optional background, border, and corner radius.
/// see [`fill`](crate::fill), [`outline`](crate::outline), and [`quad`](crate::quad) to construct this type.
pub fn paint_quad(&mut self, quad: PaintQuad) {
pub fn paint_quad(
&mut self,
quad: PaintQuad,
hover: Option<PaintQuad>,
group_hover: Option<(SharedString, Option<PaintQuad>)>,
) {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_quad(
Quad {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: quad.bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -705,6 +709,13 @@ impl<'a> ElementContext<'a> {
corner_radii: quad.corner_radii.scale(scale_factor),
border_widths: quad.border_widths.scale(scale_factor),
},
hover.map(|quad| quad.into_primitive(view_id, scale_factor, content_mask.clone())),
group_hover.map(|(group_id, quad)| {
(
group_id,
quad.map(|quad| quad.into_primitive(view_id, scale_factor, content_mask)),
)
}),
);
}
@@ -721,7 +732,7 @@ impl<'a> ElementContext<'a> {
window
.next_frame
.scene
.insert(&window.next_frame.z_index_stack, path.scale(scale_factor));
.insert_path(path.scale(scale_factor), None, None);
}
/// Paint an underline into the scene for the next frame at the current z-index.
@@ -745,11 +756,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_underline(
Underline {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -757,6 +766,8 @@ impl<'a> ElementContext<'a> {
thickness: style.thickness.scale(scale_factor),
wavy: style.wavy,
},
None,
None,
);
}
@@ -777,11 +788,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_underline(
Underline {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
@@ -789,6 +798,8 @@ impl<'a> ElementContext<'a> {
color: style.color.unwrap_or_default(),
wavy: false,
},
None,
None,
);
}
@@ -837,17 +848,17 @@ impl<'a> ElementContext<'a> {
let content_mask = self.content_mask().scale(scale_factor);
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_monochrome_sprite(
MonochromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
color,
tile,
},
None,
None,
);
}
Ok(())
@@ -895,11 +906,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_polychrome_sprite(
PolychromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
corner_radii: Default::default(),
@@ -908,6 +917,8 @@ impl<'a> ElementContext<'a> {
grayscale: false,
pad: 0,
},
None,
None,
);
}
Ok(())
@@ -941,17 +952,17 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_monochrome_sprite(
MonochromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
color,
tile,
},
None,
None,
);
Ok(())
@@ -980,11 +991,9 @@ impl<'a> ElementContext<'a> {
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_polychrome_sprite(
PolychromeSprite {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
@@ -993,28 +1002,34 @@ impl<'a> ElementContext<'a> {
grayscale,
pad: 0,
},
None,
None,
);
Ok(())
}
/// Paint a surface into the scene for the next frame at the current z-index.
#[cfg(target_os = "macos")]
pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
pub fn paint_surface(
&mut self,
bounds: Bounds<Pixels>,
image_buffer: CVImageBuffer,
occludes_hover: bool,
) {
let scale_factor = self.scale_factor();
let bounds = bounds.scale(scale_factor);
let content_mask = self.content_mask().scale(scale_factor);
let view_id = self.parent_view_id();
let window = &mut *self.window;
window.next_frame.scene.insert(
&window.next_frame.z_index_stack,
window.next_frame.scene.insert_surface(
crate::Surface {
view_id: view_id.into(),
layer_id: 0,
order: 0,
bounds,
content_mask,
image_buffer,
},
occludes_hover,
);
}
@@ -1161,6 +1176,22 @@ impl<'a> ElementContext<'a> {
})
}
/// Invoke the given function with the given hover group id present on the hover stack.
/// This is a fairly low-level method used to paint hover effects for views that share
/// the same hover group.
pub fn with_hover_group<R>(
&mut self,
name: Option<SharedString>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let window = &mut self.window;
let group = window.next_frame.scene.hover_group(name);
window.hover_group_stack.push(group);
let result = f(self);
window.hover_group_stack.pop();
result
}
/// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the
/// platform to receive textual input with proper integration with concerns such
/// as IME interactions. This handler will be active for the upcoming frame until the following frame is

View File

@@ -1,89 +0,0 @@
[package]
name = "languages"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
gpui.workspace = true
language.workspace = true
node_runtime.workspace = true
rust-embed = "8.2.0"
settings.workspace = true
tree-sitter-astro.workspace = true
tree-sitter-bash.workspace = true
tree-sitter-c-sharp.workspace = true
tree-sitter-c.workspace = true
tree-sitter-clojure.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-css.workspace = true
tree-sitter-dockerfile.workspace = true
tree-sitter-dart.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-elm.workspace = true
tree-sitter-embedded-template.workspace = true
tree-sitter-erlang.workspace = true
tree-sitter-gitcommit.workspace = true
tree-sitter-gleam.workspace = true
tree-sitter-glsl.workspace = true
tree-sitter-go.workspace = true
tree-sitter-gomod.workspace = true
tree-sitter-gowork.workspace = true
tree-sitter-haskell.workspace = true
tree-sitter-hcl.workspace = true
tree-sitter-heex.workspace = true
tree-sitter-html.workspace = true
tree-sitter-json.workspace = true
tree-sitter-lua.workspace = true
tree-sitter-markdown.workspace = true
tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-ocaml.workspace = true
tree-sitter-php.workspace = true
tree-sitter-prisma-io.workspace = true
tree-sitter-proto.workspace = true
tree-sitter-purescript.workspace = true
tree-sitter-python.workspace = true
tree-sitter-racket.workspace = true
tree-sitter-ruby.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-scheme.workspace = true
tree-sitter-svelte.workspace = true
tree-sitter-toml.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-uiua.workspace = true
tree-sitter-vue.workspace = true
tree-sitter-yaml.workspace = true
tree-sitter-zig.workspace = true
tree-sitter.workspace = true
util.workspace = true
lsp.workspace = true
async-trait = "0.1.77"
shellexpand = "3.1.0"
serde_json.workspace = true
serde_derive.workspace = true
futures.workspace = true
smol.workspace = true
toml.workspace = true
lazy_static.workspace = true
schemars.workspace = true
log.workspace = true
task.workspace = true
parking_lot.workspace = true
async-compression = "0.4.6"
collections.workspace = true
async-tar = "0.4.2"
regex.workspace = true
feature_flags.workspace = true
project.workspace = true
serde.workspace = true
rope.workspace = true
[dev-dependencies]
text.workspace = true
theme.workspace = true
unindent.workspace = true

View File

@@ -1 +0,0 @@
../../LICENSE-GPL

View File

@@ -66,7 +66,6 @@ thiserror.workspace = true
toml.workspace = true
util.workspace = true
which.workspace = true
project_core.workspace = true
[dev-dependencies]
client = { workspace = true, features = ["test-support"] }

View File

@@ -1,12 +1,14 @@
pub mod debounced_delay;
mod ignore;
pub mod lsp_command;
pub mod lsp_ext_command;
mod prettier_support;
pub mod project_settings;
pub mod search;
mod task_inventory;
pub mod terminals;
pub mod worktree;
pub use project_core::*;
#[cfg(test)]
mod project_tests;
#[cfg(test)]
@@ -55,7 +57,7 @@ use node_runtime::NodeRuntime;
use parking_lot::{Mutex, RwLock};
use postage::watch;
use prettier_support::{DefaultPrettier, PrettierInstance};
use project_core::project_settings::{LspSettings, ProjectSettings};
use project_settings::{LspSettings, ProjectSettings};
use rand::prelude::*;
use rpc::{ErrorCode, ErrorExt as _};
@@ -95,6 +97,7 @@ pub use fs::*;
#[cfg(any(test, feature = "test-support"))]
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
pub use task_inventory::Inventory;
pub use worktree::*;
const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
@@ -313,6 +316,11 @@ pub struct ProjectPath {
pub path: Arc<Path>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
pub struct DiagnosticSummary {
pub error_count: usize,
pub warning_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Location {
@@ -431,8 +439,66 @@ impl Hover {
#[derive(Default)]
pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language::Transaction>);
impl DiagnosticSummary {
fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
let mut this = Self {
error_count: 0,
warning_count: 0,
};
for entry in diagnostics {
if entry.diagnostic.is_primary {
match entry.diagnostic.severity {
DiagnosticSeverity::ERROR => this.error_count += 1,
DiagnosticSeverity::WARNING => this.warning_count += 1,
_ => {}
}
}
}
this
}
pub fn is_empty(&self) -> bool {
self.error_count == 0 && self.warning_count == 0
}
pub fn to_proto(
&self,
language_server_id: LanguageServerId,
path: &Path,
) -> proto::DiagnosticSummary {
proto::DiagnosticSummary {
path: path.to_string_lossy().to_string(),
language_server_id: language_server_id.0 as u64,
error_count: self.error_count as u32,
warning_count: self.warning_count as u32,
}
}
}
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ProjectEntryId(usize);
impl ProjectEntryId {
pub const MAX: Self = Self(usize::MAX);
pub fn new(counter: &AtomicUsize) -> Self {
Self(counter.fetch_add(1, SeqCst))
}
pub fn from_proto(id: u64) -> Self {
Self(id as usize)
}
pub fn to_proto(&self) -> u64 {
self.0 as u64
}
pub fn to_usize(&self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormatTrigger {
@@ -6722,7 +6788,7 @@ impl Project {
cx.observe(worktree, |_, _, cx| cx.notify()).detach();
if worktree.read(cx).is_local() {
cx.subscribe(worktree, |this, worktree, event, cx| match event {
project_core::worktree::Event::UpdatedEntries(changes) => {
worktree::Event::UpdatedEntries(changes) => {
this.update_local_worktree_buffers(&worktree, changes, cx);
this.update_local_worktree_language_servers(&worktree, changes, cx);
this.update_local_worktree_settings(&worktree, changes, cx);
@@ -6732,7 +6798,7 @@ impl Project {
changes.clone(),
));
}
project_core::worktree::Event::UpdatedGitRepositories(updated_repos) => {
worktree::Event::UpdatedGitRepositories(updated_repos) => {
this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx)
}
})

View File

@@ -251,7 +251,7 @@ struct BackgroundScannerState {
#[derive(Debug, Clone)]
pub struct LocalRepositoryEntry {
pub(crate) git_dir_scan_id: usize,
pub repo_ptr: Arc<Mutex<dyn GitRepository>>,
pub(crate) repo_ptr: Arc<Mutex<dyn GitRepository>>,
/// Path to the actual .git folder.
/// Note: if .git is a file, this points to the folder indicated by the .git file
pub(crate) git_dir_path: Arc<Path>,
@@ -718,7 +718,7 @@ impl LocalWorktree {
path.starts_with(&self.abs_path)
}
pub fn load_buffer(
pub(crate) fn load_buffer(
&mut self,
id: BufferId,
path: &Path,
@@ -1593,7 +1593,7 @@ impl RemoteWorktree {
self.completed_scan_id >= scan_id
}
pub fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
pub(crate) fn wait_for_snapshot(&mut self, scan_id: usize) -> impl Future<Output = Result<()>> {
let (tx, rx) = oneshot::channel();
if self.observed_snapshot(scan_id) {
let _ = tx.send(());
@@ -1659,7 +1659,7 @@ impl RemoteWorktree {
})
}
pub fn delete_entry(
pub(crate) fn delete_entry(
&mut self,
id: ProjectEntryId,
scan_id: usize,
@@ -2093,7 +2093,7 @@ impl Snapshot {
}
impl LocalSnapshot {
pub fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
pub(crate) fn get_local_repo(&self, repo: &RepositoryEntry) -> Option<&LocalRepositoryEntry> {
self.git_repositories.get(&repo.work_directory.0)
}
@@ -2744,7 +2744,7 @@ impl WorktreeId {
Self(handle_id)
}
pub fn from_proto(id: u64) -> Self {
pub(crate) fn from_proto(id: u64) -> Self {
Self(id as usize)
}
@@ -2829,10 +2829,10 @@ pub struct File {
pub worktree: Model<Worktree>,
pub path: Arc<Path>,
pub mtime: SystemTime,
pub entry_id: Option<ProjectEntryId>,
pub is_local: bool,
pub is_deleted: bool,
pub is_private: bool,
pub(crate) entry_id: Option<ProjectEntryId>,
pub(crate) is_local: bool,
pub(crate) is_deleted: bool,
pub(crate) is_private: bool,
}
impl language::File for File {

View File

@@ -1,35 +0,0 @@
[package]
name = "project_core"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
client.workspace = true
clock.workspace = true
collections.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
git.workspace = true
gpui.workspace = true
ignore = "0.4.22"
itertools = "0.12.1"
language.workspace = true
log.workspace = true
lsp.workspace = true
parking_lot.workspace = true
postage.workspace = true
rpc.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
schemars.workspace = true
smol.workspace = true
sum_tree.workspace = true
text.workspace = true
util.workspace = true

View File

@@ -1,80 +0,0 @@
pub mod worktree;
pub use worktree::*;
mod ignore;
pub mod project_settings;
use serde::Serialize;
use fs::{copy_recursive, RemoveOptions};
use std::sync::atomic::Ordering::SeqCst;
use std::sync::atomic::AtomicUsize;
use rpc::proto;
use std::path::Path;
use lsp::LanguageServerId;
use lsp::DiagnosticSeverity;
use language::DiagnosticEntry;
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize)]
pub struct DiagnosticSummary {
pub error_count: usize,
pub warning_count: usize,
}
impl DiagnosticSummary {
fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
let mut this = Self {
error_count: 0,
warning_count: 0,
};
for entry in diagnostics {
if entry.diagnostic.is_primary {
match entry.diagnostic.severity {
DiagnosticSeverity::ERROR => this.error_count += 1,
DiagnosticSeverity::WARNING => this.warning_count += 1,
_ => {}
}
}
}
this
}
pub fn is_empty(&self) -> bool {
self.error_count == 0 && self.warning_count == 0
}
pub fn to_proto(
&self,
language_server_id: LanguageServerId,
path: &Path,
) -> proto::DiagnosticSummary {
proto::DiagnosticSummary {
path: path.to_string_lossy().to_string(),
language_server_id: language_server_id.0 as u64,
error_count: self.error_count as u32,
warning_count: self.warning_count as u32,
}
}
}
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ProjectEntryId(usize);
impl ProjectEntryId {
pub const MAX: Self = Self(usize::MAX);
pub fn new(counter: &AtomicUsize) -> Self {
Self(counter.fetch_add(1, SeqCst))
}
pub fn from_proto(id: u64) -> Self {
Self(id as usize)
}
pub fn to_proto(&self) -> u64 {
self.0 as u64
}
pub fn to_usize(&self) -> usize {
self.0
}
}

View File

@@ -10,6 +10,7 @@ path = "src/recent_projects.rs"
doctest = false
[dependencies]
editor.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
@@ -25,3 +26,6 @@ theme.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
[dev-dependencies]
editor = { workspace = true, features = ["test-support"] }

View File

@@ -8,6 +8,7 @@ license = "GPL-3.0-or-later"
[dependencies]
anyhow.workspace = true
db.workspace = true
editor.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true

View File

@@ -133,7 +133,7 @@ impl LayoutRect {
)
.into();
cx.paint_quad(fill(Bounds::new(position, size), self.color));
cx.paint_quad(fill(Bounds::new(position, size), self.color), None, None);
}
}
@@ -782,7 +782,7 @@ impl Element for TerminalElement {
) {
let mut layout = self.compute_layout(bounds, cx);
cx.paint_quad(fill(bounds, layout.background_color));
cx.paint_quad(fill(bounds, layout.background_color), None, None);
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
let terminal_input_handler = TerminalInputHandler {

View File

@@ -11,6 +11,7 @@ doctest = false
[dependencies]
client.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
fuzzy.workspace = true

View File

@@ -766,7 +766,11 @@ mod element {
}
cx.add_opaque_layer(handle_bounds);
cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
cx.paint_quad(
gpui::fill(divider_bounds, cx.theme().colors().border),
None,
None,
);
cx.on_mouse_event({
let dragged_handle = dragged_handle.clone();

View File

@@ -113,6 +113,52 @@ theme_selector.workspace = true
thiserror.workspace = true
tiny_http = "0.8"
toml.workspace = true
tree-sitter-astro.workspace = true
tree-sitter-bash.workspace = true
tree-sitter-c-sharp.workspace = true
tree-sitter-c.workspace = true
tree-sitter-clojure.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-css.workspace = true
tree-sitter-dockerfile.workspace = true
tree-sitter-dart.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-elm.workspace = true
tree-sitter-embedded-template.workspace = true
tree-sitter-erlang.workspace = true
tree-sitter-gitcommit.workspace = true
tree-sitter-gleam.workspace = true
tree-sitter-glsl.workspace = true
tree-sitter-go.workspace = true
tree-sitter-gomod.workspace = true
tree-sitter-gowork.workspace = true
tree-sitter-haskell.workspace = true
tree-sitter-hcl.workspace = true
tree-sitter-heex.workspace = true
tree-sitter-html.workspace = true
tree-sitter-json.workspace = true
tree-sitter-lua.workspace = true
tree-sitter-markdown.workspace = true
tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-ocaml.workspace = true
tree-sitter-php.workspace = true
tree-sitter-prisma-io.workspace = true
tree-sitter-proto.workspace = true
tree-sitter-purescript.workspace = true
tree-sitter-python.workspace = true
tree-sitter-racket.workspace = true
tree-sitter-ruby.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-scheme.workspace = true
tree-sitter-svelte.workspace = true
tree-sitter-toml.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-uiua.workspace = true
tree-sitter-vue.workspace = true
tree-sitter-yaml.workspace = true
tree-sitter-zig.workspace = true
tree-sitter.workspace = true
url.workspace = true
urlencoding = "2.1.2"
util.workspace = true
@@ -121,8 +167,6 @@ vim.workspace = true
welcome.workspace = true
workspace.workspace = true
zed_actions.workspace = true
languages = { version = "0.1.0", path = "../languages" }
[dev-dependencies]
call = { workspace = true, features = ["test-support"] }
@@ -131,7 +175,6 @@ gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
tree-sitter-rust.workspace = true
unindent.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View File

@@ -55,7 +55,7 @@ mod zig;
// 6. If the language has injections add an injections.scm query file
#[derive(RustEmbed)]
#[folder = "src/"]
#[folder = "src/languages"]
#[exclude = "*.rs"]
struct LanguageDir;

View File

@@ -296,7 +296,7 @@ mod tests {
});
});
});
let language = crate::language("c", tree_sitter_c::language(), None).await;
let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
cx.new_model(|cx| {
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")

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