Files
taskwarrior-gpui/src/ui.rs
T
Ignacio Perez 00cfc3ff4d feat(modal): add edit mode with anchored dropdowns and keyboard workflow
- Add TaskDetailModal edit mode with form validation, tags + annotations editing, and SaveEdits event
- Anchor dropdown/suggestion menus using deferred/anchored overlay to avoid clipping
- Add modal-specific keymap contexts (ModalInput/ModalDropdown) for Esc/Ctrl+Enter/Tab navigation
- Refresh modal project suggestions from tasks; improve project child filtering boundary match
- Small UI polish (toast styling, layout spacing) and update shortcuts docs
2026-01-02 10:38:25 -03:00

197 lines
4.8 KiB
Rust

use gpui::prelude::*;
use gpui::{Pixels, px, rems};
use crate::theme::{Color, Theme};
pub const CARD_RADIUS: Pixels = px(6.0);
pub const CARD_PADDING: Pixels = px(8.0);
pub const SECTION_GAP: Pixels = px(12.0);
pub const INSET_GAP: Pixels = px(8.0);
pub const ROOT_PADDING: Pixels = px(12.0);
pub const CONTROL_RADIUS: Pixels = px(6.0);
pub const CONTROL_BORDER: Pixels = px(1.0);
pub const SIDEBAR_WIDTH: Pixels = px(250.0);
pub const TABLE_MAX_DESCRIPTION_LENGTH: usize = 50;
pub const TABLE_FILTER_BAR_INITIAL_HEIGHT: Pixels = px(52.0);
#[inline(always)]
pub fn table_col_id_width() -> gpui::Rems {
rems(3.0)
}
#[inline(always)]
pub fn table_col_desc_min_width() -> gpui::Rems {
rems(10.0)
}
#[inline(always)]
pub fn table_col_project_width() -> gpui::Rems {
rems(10.0)
}
#[inline(always)]
pub fn table_col_due_width() -> gpui::Rems {
rems(7.0)
}
#[inline(always)]
pub fn table_col_priority_width() -> gpui::Rems {
rems(5.0)
}
#[inline(always)]
pub fn table_col_status_width() -> gpui::Rems {
rems(6.0)
}
pub const DATE_FORMAT: &str = "%Y-%m-%d";
pub const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M";
pub fn card_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
div.bg(theme.card)
.border_1()
.border_color(theme.divider)
.rounded(CARD_RADIUS)
.p(CARD_PADDING)
}
pub fn raised_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
div.bg(theme.raised)
.border_1()
.border_color(theme.divider)
.rounded(CARD_RADIUS)
.p(CARD_PADDING)
}
pub fn panel_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
div.bg(theme.panel)
.border_1()
.border_color(theme.border)
.rounded(CARD_RADIUS)
}
pub fn field_style(div: gpui::Div, theme: &Theme, focused: bool) -> gpui::Div {
div.bg(theme.field_bg)
.border_1()
.border_color(if focused {
theme.focus_ring
} else {
theme.field_border
})
.rounded(CARD_RADIUS)
.px_2()
.py_1()
}
pub fn divider_h(theme: &Theme) -> gpui::Div {
gpui::div().w_full().h(px(1.0)).bg(theme.divider)
}
pub fn divider_v(theme: &Theme) -> gpui::Div {
gpui::div().h_full().w(px(1.0)).bg(theme.divider)
}
pub fn section_header(label: &str, theme: &Theme) -> gpui::Div {
gpui::div()
.px_2()
.py_1()
.text_xs()
.text_color(theme.muted)
.child(label.to_uppercase())
}
pub fn priority_badge(priority: &str, theme: &Theme) -> gpui::Div {
let (bg, fg) = match priority {
"High" | "H" => (Theme::alpha(theme.high, 0.15), theme.high),
"Medium" | "M" => (Theme::alpha(theme.medium, 0.15), theme.medium),
"Low" | "L" => (Theme::alpha(theme.low, 0.15), theme.low),
_ => (gpui::rgba(0x00000000), theme.muted),
};
gpui::div()
.px_2()
.py(px(2.0))
.rounded(px(4.0))
.bg(bg)
.text_color(fg)
.text_xs()
.font_weight(gpui::FontWeight::MEDIUM)
.child(priority.to_string())
}
pub fn control_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
div.flex()
.items_center()
.gap_2()
.px_3()
.py_2()
.rounded(CONTROL_RADIUS)
.border(CONTROL_BORDER)
.border_color(theme.border)
.bg(theme.background)
.text_sm()
.text_color(theme.foreground)
.whitespace_nowrap()
}
pub fn clickable_control_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
control_style(div, theme)
.cursor_pointer()
.hover(|s| s.bg(theme.hover))
}
pub fn disabled_control_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
control_style(div, theme)
.text_color(theme.muted)
.cursor_not_allowed()
}
pub fn ghost_button_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
div.flex()
.items_center()
.gap_2()
.px_2()
.py_1()
.rounded(CONTROL_RADIUS)
.text_sm()
.text_color(theme.foreground)
.cursor_pointer()
.hover(|s| s.bg(theme.hover))
}
pub fn text_button_style(div: gpui::Div, theme: &Theme) -> gpui::Div {
div.flex()
.items_center()
.gap_1()
.text_sm()
.text_color(theme.muted)
.cursor_pointer()
.hover(|s| s.text_color(theme.accent))
}
pub fn mix_color(base: Color, tint: Color, amount: f32) -> Color {
let amount = amount.clamp(0.0, 1.0);
gpui::Rgba {
r: base.r + (tint.r - base.r) * amount,
g: base.g + (tint.g - base.g) * amount,
b: base.b + (tint.b - base.b) * amount,
a: 1.0,
}
}
pub fn focus_wrap(child: impl IntoElement, focused: bool, theme: &Theme) -> gpui::Div {
let border_color = if focused {
theme.focus_ring
} else {
gpui::rgba(0x00000000)
};
gpui::div()
.border_2()
.border_color(border_color)
.rounded_md()
.child(child)
}