Files
galaxy/app/src/editor/autosuggestion_ignore_view.rs
T

163 lines
5.9 KiB
Rust

//! This module contains the code for the ignore button shown inline next to autosuggestions.
use galaxy_core::ui::theme::Fill;
use galaxyui::elements::{
ChildAnchor, ConstrainedBox, Container, CornerRadius, Element, Hoverable, MouseStateHandle,
OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Radius, Stack,
};
use galaxyui::platform::Cursor;
use galaxyui::ui_components::components::UiComponent;
use galaxyui::{Entity, SingletonEntity, TypedActionView, View, ViewContext};
use pathfinder_geometry::vector::vec2f;
use super::EditorElement;
use crate::appearance::Appearance;
use crate::ui_components::blended_colors;
use crate::ui_components::icons::Icon;
pub const AUTOSUGGESTION_IGNORE_MINIMUM_HEIGHT: f32 = 12.;
pub struct AutosuggestionIgnore {
autosuggestion_ignore_mouse_handle: MouseStateHandle,
current_autosuggestion: Option<String>,
}
pub enum AutosuggestionIgnoreEvent {
IgnoreAutosuggestion { suggestion: String },
}
impl Default for AutosuggestionIgnore {
fn default() -> Self {
Self::new()
}
}
impl Entity for AutosuggestionIgnore {
type Event = AutosuggestionIgnoreEvent;
}
impl AutosuggestionIgnore {
pub fn new() -> Self {
Self {
autosuggestion_ignore_mouse_handle: Default::default(),
current_autosuggestion: None,
}
}
pub fn set_current_autosuggestion(&mut self, suggestion: Option<String>) {
self.current_autosuggestion = suggestion;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AutosuggestionIgnoreAction {
IgnoreCurrentAutosuggestion,
}
impl TypedActionView for AutosuggestionIgnore {
type Action = AutosuggestionIgnoreAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
AutosuggestionIgnoreAction::IgnoreCurrentAutosuggestion => {
if let Some(suggestion) = &self.current_autosuggestion {
ctx.emit(AutosuggestionIgnoreEvent::IgnoreAutosuggestion {
suggestion: suggestion.clone(),
});
}
}
}
ctx.notify();
}
}
impl View for AutosuggestionIgnore {
fn ui_name() -> &'static str {
"AutosuggestionIgnore"
}
fn render(&self, ctx: &galaxyui::AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(ctx);
// Because the icon's origin is NOT the top of the line but the top of the cursor,
// we should render it with line height ratio no larger than DEFAULT_UI_LINE_HEIGHT_RATIO.
// With larger line height ratios, there wouldn't be enough space in the icon to render the text.
// But we do need to account for smaller line height ratios so the text can be rendered in the smaller space.
let line_height_ratio = appearance
.line_height_ratio()
.min(galaxyui::elements::DEFAULT_UI_LINE_HEIGHT_RATIO);
// We want the ignore icon to be the same height as the cursor in the input.
let height =
EditorElement::cursor_height(appearance.monospace_font_size(), line_height_ratio)
.max(AUTOSUGGESTION_IGNORE_MINIMUM_HEIGHT);
let disabled_color = blended_colors::semantic_text_disabled(appearance.theme());
let border_width = 1.;
// The stack contains the ignore button and tooltip that shows upon mouse hover.
let mut stack = Stack::new();
Hoverable::new(self.autosuggestion_ignore_mouse_handle.clone(), |state| {
// Colors are inverted when hovered.
let (icon_color, background_color) = if state.is_hovered() {
(
appearance.theme().background().into(),
Some(blended_colors::semantic_text_disabled(appearance.theme())),
)
} else {
(
blended_colors::semantic_text_disabled(appearance.theme()),
None,
)
};
let height_without_border = height - border_width * 2.;
let close_icon = Container::new(
ConstrainedBox::new(Icon::X.to_galaxyui_icon(Fill::Solid(icon_color)).finish())
.with_height(height_without_border)
.with_width(height_without_border)
.finish(),
)
.finish();
let mut ignore_button = Container::new(close_icon)
.with_uniform_padding(2.)
.with_border(
galaxyui::elements::Border::all(border_width).with_border_color(disabled_color),
)
.with_corner_radius(CornerRadius::with_all(Radius::Percentage(25.)));
if let Some(background_color) = background_color {
ignore_button = ignore_button.with_background_color(background_color);
}
let ignore_button_element = ConstrainedBox::new(ignore_button.finish())
.with_max_height(height)
.finish();
stack.add_child(ignore_button_element);
if state.is_hovered() {
let tool_tip = appearance
.ui_builder()
.autosuggestion_tool_tip("Ignore this suggestion".into())
.build()
.finish();
stack.add_positioned_overlay_child(
tool_tip,
OffsetPositioning::offset_from_parent(
vec2f(0., -5.),
ParentOffsetBounds::WindowByPosition,
ParentAnchor::TopMiddle,
ChildAnchor::BottomMiddle,
),
);
}
stack.finish()
})
.on_click(|ctx, _, _| {
ctx.dispatch_typed_action(AutosuggestionIgnoreAction::IgnoreCurrentAutosuggestion);
})
.with_cursor(Cursor::PointingHand)
.finish()
}
}