Compare commits

...

3 Commits

Author SHA1 Message Date
Nate Butler
21258ad3f7 wip - need to complete path 2024-07-11 11:21:11 -04:00
Nate Butler
b4377577e4 Update segment types 2024-07-11 10:41:19 -04:00
Nate Butler
a476d335ab Add tree branch component 2024-07-11 10:18:36 -04:00
3 changed files with 218 additions and 0 deletions

View File

@@ -38,6 +38,7 @@ pub enum ComponentStory {
Text,
ToggleButton,
ToolStrip,
TreeBranch,
ViewportUnits,
WithRemSize,
}
@@ -72,6 +73,7 @@ impl ComponentStory {
Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
Self::ToolStrip => cx.new_view(|_| ui::ToolStripStory).into(),
Self::TreeBranch => cx.new_view(|_| ui::TreeBranchStory).into(),
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
Self::WithRemSize => cx.new_view(|_| crate::stories::WithRemSizeStory).into(),
Self::Picker => PickerStory::new(cx).into(),

View File

@@ -23,6 +23,7 @@ mod tab;
mod tab_bar;
mod tool_strip;
mod tooltip;
mod tree_branch;
#[cfg(feature = "stories")]
mod stories;
@@ -52,6 +53,7 @@ pub use tab::*;
pub use tab_bar::*;
pub use tool_strip::*;
pub use tooltip::*;
pub use tree_branch::*;
#[cfg(feature = "stories")]
pub use stories::*;

View File

@@ -0,0 +1,214 @@
use gpui::{canvas, fill, point, Bounds, Hsla, Path};
use strum::EnumIter;
use crate::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, EnumIter)]
/// Represents different segments of a tree branch in a tree-like structure.
pub enum TreeBranchSegment {
/// A full height segment that continues both upward and downward.
///
/// It can be used for both full height continuous branches and for
/// the middle segment of a branch that has space on the top and bottom.
///
/// Used for segments that connect upper and lower parts of a branch.
///
/// Example:
/// ```
/// Parent
/// │
/// Continuous
/// │
/// └─ Child
/// ```
Continuous,
/// The start of a continuous segment.
///
/// Used for the beginning of a continuous branch.
///
/// Example:
/// ```
/// Parent
/// │
/// ContinuousStart
/// │
/// Continuous
/// ```
ContinuousStart,
/// The end of a continuous segment.
///
/// Used for the end of a continuous branch.
///
/// Example:
/// ```
/// Continuous
/// │
/// ContinuousEnd
/// └─ Child
/// ```
ContinuousEnd,
/// The start of a leaf (includes a horizontal line).
///
/// Used for segments that begin a leaf node, typically with children.
///
/// Example:
/// ```
/// Parent
/// ├─ LeafStart
/// │ └─ Child
/// └─ Sibling
/// ```
LeafStart,
/// The end of a leaf (no continuation downward).
///
/// Used for the last segment of a branch, typically for childless nodes.
///
/// Example:
/// ```
/// Parent
/// ├─ Sibling
/// └─ LeafEnd
/// ```
LeafEnd,
}
#[derive(IntoElement)]
pub struct TreeBranch {
/// The width of the branch.
width: Pixels,
/// The height of the branch.
height: Pixels,
/// The color of the branch.
color: Hsla,
/// The segment of the branch.
segment: TreeBranchSegment,
/// Whether the branch should draw beyond it's
/// set height. This is useful for connecting
/// branches that have a slight gap between them
/// due to a margin or padding.
overdraw: bool,
/// The length of the overdraw.
overdraw_length: Pixels,
}
impl TreeBranch {
pub fn new(segment: TreeBranchSegment, cx: &WindowContext) -> Self {
let rem_size = cx.rem_size();
let line_height = cx.text_style().line_height_in_pixels(rem_size);
let width = line_height * 1.5;
let height = line_height;
let color = cx.theme().colors().icon_placeholder;
let overdraw_length = px(1.);
Self {
width,
height,
color,
segment,
overdraw: false,
overdraw_length,
}
}
pub fn width(&mut self, width: Pixels) -> &mut Self {
self.width = width;
self
}
pub fn height(&mut self, height: Pixels) -> &mut Self {
self.height = height;
self
}
pub fn overdraw(&mut self, overdraw: bool) -> &mut Self {
self.overdraw = overdraw;
self
}
pub fn overdraw_length(&mut self, overdraw_length: Pixels) -> &mut Self {
self.overdraw_length = overdraw_length;
self
}
}
impl RenderOnce for TreeBranch {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let thickness = px(1.);
let has_start_gap = matches!(self.segment, TreeBranchSegment::LeafStart | TreeBranchSegment::ContinuousStart);
let gap_before_start = px(4.);
let has_end_gap = matches!(self.segment, TreeBranchSegment::LeafEnd | TreeBranchSegment::ContinuousEnd);
let last_segment_height = self.height / 2.;
canvas(
|_, _| {},
move |bounds, _, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = bounds.top();
let end_y = if has_end_gap {
start_y + last_segment_height
} else {
bounds.bottom()
};
let mut path = Path::new(point(start_x, start_y));
// Vertical line
path.line_to(point(start_x, end_y));
// Horizontal line for LeafStart and LeafEnd
if matches!(self.segment, TreeBranchSegment::LeafStart | TreeBranchSegment::LeafEnd) {
let mid_y = (start_y + end_y) / 2.;
let curve_radius = px(3.);
let ctrl_point = point(start_x + curve_radius / 2., mid_y - curve_radius / 2.);
let end_point = point(start_x + curve_radius, mid_y);
path.line_to(point(start_x, mid_y));
path.curve_to(end_point, ctrl_point);
path.line_to(point(bounds.right(), mid_y));
}
// Draw the path
cx.paint_path(path, self.color);
},
)
.w(self.width)
.h(self.height)
}
}
#[cfg(feature = "stories")]
mod stories {
use gpui::Render;
use story::{StoryContainer, StoryItem, StorySection};
use strum::IntoEnumIterator;
use crate::prelude::*;
use super::{TreeBranch, TreeBranchSegment};
pub struct TreeBranchStory;
impl Render for TreeBranchStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
StoryContainer::new("TreeBranch", "crates/ui/src/components/tree_branch.rs")
.child(
StorySection::new()
.children(
// iter over all the segments
TreeBranchSegment::iter().map(|segment| {
StoryItem::new(format!("{:?}", segment), TreeBranch::new(segment, cx))
}),
)
)
}
}
}
pub use stories::TreeBranchStory;