From 7c1824dcf7eaaff27a26efc9f1841f60de646ace Mon Sep 17 00:00:00 2001 From: Ignacio Perez Date: Wed, 24 Dec 2025 19:21:04 -0300 Subject: [PATCH] feat: Add GPUI component library Add core UI components for TaskWarrior GUI application: Components: - Input: Text input with autocomplete suggestions, keyboard navigation - Button: Multiple variants (Primary, Secondary, Ghost, Danger, Success, Text) - DropdownButton: Compound button with dropdown menu - Icon: SVG icons with customizable sizes - Label: Simple text display - Panel: Container with optional title - List: List container - Modal: Modal dialog - Divider: Visual separator Infrastructure: - Theme system with dark/light modes (Catppuccin colors) - App module with window management --- src/app.rs | 82 +++++ src/components/button/dropdown.rs | 261 +++++++++++++ src/components/button/mod.rs | 287 +++++++++++++++ src/components/divider.rs | 46 +++ src/components/icon.rs | 162 +++++++++ src/components/input/mod.rs | 567 +++++++++++++++++++++++++++++ src/components/input/suggestion.rs | 24 ++ src/components/label.rs | 31 ++ src/components/list.rs | 198 ++++++++++ src/components/mod.rs | 8 + src/components/modal.rs | 369 +++++++++++++++++++ src/components/panel.rs | 88 +++++ src/main.rs | 92 +---- src/theme.rs | 70 ++++ 14 files changed, 2198 insertions(+), 87 deletions(-) create mode 100644 src/app.rs create mode 100644 src/components/button/dropdown.rs create mode 100644 src/components/button/mod.rs create mode 100644 src/components/divider.rs create mode 100644 src/components/icon.rs create mode 100644 src/components/input/mod.rs create mode 100644 src/components/input/suggestion.rs create mode 100644 src/components/label.rs create mode 100644 src/components/list.rs create mode 100644 src/components/mod.rs create mode 100644 src/components/modal.rs create mode 100644 src/components/panel.rs create mode 100644 src/theme.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..cbed26b --- /dev/null +++ b/src/app.rs @@ -0,0 +1,82 @@ +use gpui::prelude::*; +use std::sync::Arc; + +use crate::components::{ + self, + input::{Input, Suggestion}, +}; + +pub(crate) struct App { + input: gpui::Entity, + current_text: gpui::SharedString, +} + +impl gpui::Render for App { + fn render( + &mut self, + _window: &mut gpui::Window, + _cx: &mut gpui::Context, + ) -> impl gpui::IntoElement { + components::panel::Panel::new() + .child(self.input.clone()) + .child(components::label::Label::new(format!( + "Text: {}", + &self.current_text + ))) + } +} + +impl App { + pub fn run() -> () { + let app = gpui::Application::new(); + + app.run(|app: &mut gpui::App| { + app.set_global(crate::theme::Theme::dark()); + app.open_window( + gpui::WindowOptions::default(), + |_window: &mut gpui::Window, app: &mut gpui::App| { + app.new(|cx: &mut gpui::Context<'_, App>| { + let input = cx.new(|cx| { + Input::new("input", cx, "Type here...").with_suggest(Arc::new(|text| { + let suggestions = vec![ + ("project:", "Filter by project"), + ("+work", "Tag: work"), + ("+personal", "Tag: personal"), + ("due:today", "Tasks for today"), + ("due:tomorrow", "Tasks for tomorrow"), + ("priority:H", "Priority high"), + ("priority:M", "Priority medium"), + ("priority:L", "Priority low"), + ]; + + suggestions + .into_iter() + .filter(|(insert, _)| { + text.is_empty() + || insert.to_lowercase().contains(&text.to_lowercase()) + }) + .map(|(insert, label)| { + Suggestion::new(format!("{} - {}", insert, label), insert) + }) + .collect() + })) + }); + + cx.observe(&input, |this, input, cx| { + let value = input.read(cx).value().to_string(); + this.current_text = value.into(); + cx.notify(); + }) + .detach(); + + App { + input, + current_text: "".into(), + } + }) + }, + ) + .unwrap(); + }); + } +} diff --git a/src/components/button/dropdown.rs b/src/components/button/dropdown.rs new file mode 100644 index 0000000..708fbfe --- /dev/null +++ b/src/components/button/dropdown.rs @@ -0,0 +1,261 @@ +use std::sync::Arc; + +use gpui::prelude::*; + +use crate::components::button::Button; +use crate::theme::ActiveTheme; + +#[derive(Clone, Debug)] +pub struct DropdownItem { + pub label: gpui::SharedString, +} + +impl DropdownItem { + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + } + } +} + +pub struct Dropdown { + id: gpui::ElementId, + button: Option