use gpui::{
App, AppContext, Application, Bounds, Context, Entity, IntoElement, ParentElement, Render,
Size as GpuiSize, Styled, Subscription, Window, WindowBounds, WindowOptions, div,
prelude::FluentBuilder as _, px,
};
use woocraft::{
ActiveTheme, Button, ButtonVariants as _, IconName, PopupMenuItem, Selectable, Sizable,
StyledExt, Theme, ThemeMode, TitleBar, TreeEvent, TreeItem, TreeState, h_flex, init, tree,
v_flex, window_border,
};
fn demo_tree(expanded: bool) -> Vec<TreeItem> {
vec![
TreeItem::new("src", "src")
.icon(IconName::Folder)
.expanded(expanded)
.child(
TreeItem::new("src/base", "base")
.icon(IconName::Folder)
.expanded(expanded)
.children([
TreeItem::new("src/base/style.rs", "style.rs").icon(IconName::Code),
TreeItem::new("src/base/theme.rs", "theme.rs").icon(IconName::Code),
TreeItem::new("src/base/tree.rs", "tree.rs").icon(IconName::Code),
]),
)
.child(
TreeItem::new("src/widgets", "widgets")
.icon(IconName::Folder)
.expanded(expanded)
.children([
TreeItem::new("src/widgets/button.rs", "button.rs").icon(IconName::Code),
TreeItem::new("src/widgets/input.rs", "input.rs").icon(IconName::Code),
TreeItem::new("src/widgets/tree.rs", "tree.rs").icon(IconName::Code),
]),
),
TreeItem::new("examples", "examples")
.icon(IconName::Folder)
.expanded(expanded)
.children([
TreeItem::new("examples/controls.rs", "controls.rs").icon(IconName::Code),
TreeItem::new("examples/tree.rs", "tree.rs").icon(IconName::Code),
]),
TreeItem::new("Cargo.toml", "Cargo.toml").icon(IconName::DocumentText),
TreeItem::new("README.md", "README.md")
.icon(IconName::DocumentText)
.disabled(true),
]
}
struct TreeWindow {
tree_state: Entity<TreeState>,
last_event: String,
multi_select: bool,
drag_enabled: bool,
_subscriptions: Vec<Subscription>,
}
impl TreeWindow {
fn view(_window: &mut Window, cx: &mut App) -> Entity<Self> {
let tree_state = cx.new(|cx| {
TreeState::new(cx)
.items(demo_tree(true))
.multi_selectable(false)
.draggable(false)
});
cx.new(|cx| {
let subscriptions =
vec![
cx.subscribe(&tree_state, |this: &mut Self, _, event: &TreeEvent, cx| {
this.last_event = match event {
TreeEvent::Select(ix) => format!("Select({ix})"),
TreeEvent::DoubleClicked(ix) => format!("DoubleClicked({ix})"),
TreeEvent::RightClicked(ix) => format!("RightClicked({ix})"),
TreeEvent::ClearSelection => "ClearSelection".to_string(),
TreeEvent::ToggleSelect(ix) => format!("ToggleSelect({ix})"),
TreeEvent::RangeSelect(ix) => format!("RangeSelect({ix})"),
TreeEvent::MoveItem(from, to) => format!("MoveItem({from} -> {to})"),
};
cx.notify();
}),
];
Self {
tree_state,
last_event: "Ready".to_string(),
multi_select: false,
drag_enabled: false,
_subscriptions: subscriptions,
}
})
}
}
impl Render for TreeWindow {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_dark = cx.theme().mode.is_dark();
let selected_info = {
let state = self.tree_state.read(cx);
if self.multi_select {
let indices = state.model().selected_indices();
if indices.is_empty() {
"none".to_string()
} else {
format!("{} items", indices.len())
}
} else if let Some(item) = state.selected_item() {
format!("{}", item.label)
} else {
"none".to_string()
}
};
let tree_state_for_menu = self.tree_state.clone();
window_border().child(
v_flex()
.size_full()
.min_h_0()
.child(TitleBar::new().title("Woocraft Tree Example"))
.child(
v_flex()
.size_full()
.min_h_0()
.p_6()
.gap_4()
.child(div().text_xl().font_semibold().child("Tree Component"))
.child(
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child("Click row or use keyboard (Up/Down/Left/Right/Enter) to navigate. Right-click for context menu."),
)
.child(
h_flex()
.gap_3()
.child(
Button::new("tree-theme-light")
.label("Light")
.small()
.selected(!is_dark)
.on_click(|_, _, cx| Theme::set_mode(ThemeMode::Light, cx)),
)
.child(
Button::new("tree-theme-dark")
.label("Dark")
.small()
.selected(is_dark)
.on_click(|_, _, cx| Theme::set_mode(ThemeMode::Dark, cx)),
)
.child(
Button::new("tree-expand-all")
.label("Expand All")
.small()
.on_click(cx.listener(|this, _, _, cx| {
this.tree_state.update(cx, |state, cx| {
state.set_items(demo_tree(true), cx);
});
})),
)
.child(
Button::new("tree-collapse-all")
.label("Collapse All")
.small()
.on_click(cx.listener(|this, _, _, cx| {
this.tree_state.update(cx, |state, cx| {
state.set_items(demo_tree(false), cx);
});
})),
)
.child(
Button::new("tree-toggle-multi")
.label(if self.multi_select {
"Multi-Select: ON"
} else {
"Multi-Select: OFF"
})
.small()
.when(self.multi_select, |this| this.primary())
.on_click(cx.listener(|this, _, _, cx| {
this.multi_select = !this.multi_select;
let enabled = this.multi_select;
this.tree_state.update(cx, |state, cx| {
state.multi_selectable = enabled;
state.model_mut().set_multi_selectable(enabled);
state.clear_selection(cx);
});
cx.notify();
})),
)
.child(
Button::new("tree-toggle-drag")
.label(if self.drag_enabled {
"Drag: ON"
} else {
"Drag: OFF"
})
.small()
.when(self.drag_enabled, |this| this.primary())
.on_click(cx.listener(|this, _, _, cx| {
this.drag_enabled = !this.drag_enabled;
let enabled = this.drag_enabled;
this.tree_state.update(cx, |state, _| {
state.draggable = enabled;
});
cx.notify();
})),
),
)
.child(
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child(format!(
"Selected: {} | Last Event: {}",
selected_info, self.last_event
)),
)
.child(
div()
.flex_grow()
.min_h_0()
.border_1()
.border_color(cx.theme().border)
.rounded(cx.theme().radius_container)
.bg(cx.theme().card)
.p_2()
.child(
tree(&self.tree_state).context_menu(move |ix, entry, menu, _window, _cx| {
let label = entry.item().label.clone();
let is_folder = entry.is_folder();
let item_id = entry.item().id.clone();
let tree_entity = tree_state_for_menu.clone();
menu
.item(PopupMenuItem::label(format!("{}", label)))
.separator()
.item(
PopupMenuItem::new(if is_folder {
"Toggle Expand"
} else {
"Open File"
})
.on_click(move |_, _, _cx| {
println!(
"tree example: action on {} ({})",
label,
if is_folder { "folder" } else { "file" }
);
}),
)
.item(
PopupMenuItem::new("Select This Item").on_click(
move |_, _, cx| {
tree_entity.update(cx, |state, cx| {
state.set_selected_index(Some(ix), cx);
});
},
),
)
.item(PopupMenuItem::new("Print Info").on_click(
move |_, _, _| {
println!("tree example: item id={}", item_id);
},
))
}),
),
),
),
)
}
}
fn main() {
let app = Application::new().with_assets(woocraft::Assets);
app.run(|cx: &mut App| {
init(cx);
cx.activate(true);
let bounds = Bounds::centered(None, GpuiSize::new(px(980.), px(700.)), cx);
let window = cx
.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
titlebar: Some(TitleBar::title_bar_options()),
#[cfg(target_os = "linux")]
window_background: gpui::WindowBackgroundAppearance::Transparent,
#[cfg(target_os = "linux")]
window_decorations: Some(gpui::WindowDecorations::Client),
..Default::default()
},
TreeWindow::view,
)
.expect("open tree demo window failed");
window
.update(cx, |_, window, _| {
window.activate_window();
window.set_window_title("Woocraft Tree Example");
})
.expect("update tree demo window failed");
});
}