Files
galaxy/app/src/workspace/header_toolbar_editor.rs
T

379 lines
12 KiB
Rust

use galaxyui::keymap::FixedBinding;
use galaxyui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
use settings::Setting as _;
use crate::chip_configurator::{
render_chip_editor_modal, render_chip_editor_sections, ChipConfigurator,
ChipConfiguratorAction, ChipConfiguratorLayout, ChipEditorModalConfig, ChipEditorMouseHandles,
ChipEditorSectionsConfig, ConfigurableItem, ControlItemRenderer,
};
use crate::workspace::header_toolbar_item::HeaderToolbarItemKind;
use crate::workspace::tab_settings::{
HeaderToolbarChipSelection, TabSettings, TabSettingsChangedEvent,
};
use crate::{report_if_error, Appearance};
const MODAL_TITLE: &str = "Edit toolbar";
pub fn init(app: &mut AppContext) {
use galaxyui::keymap::macros::*;
app.register_fixed_bindings([FixedBinding::new(
"escape",
HeaderToolbarEditorAction::Cancel,
id!(HeaderToolbarEditorModal::ui_name()),
)]);
}
pub enum HeaderToolbarEditorEvent {
Close,
}
pub struct HeaderToolbarEditorModal {
mouse_handles: ChipEditorMouseHandles,
chip_configurator: ChipConfigurator,
is_dirty: bool,
}
pub struct HeaderToolbarInlineEditor {
mouse_handles: ChipEditorMouseHandles,
chip_configurator: ChipConfigurator,
}
#[derive(Clone, Copy, Debug)]
pub enum HeaderToolbarEditorAction {
Cancel,
Save,
Chip(ChipConfiguratorAction),
ResetDefault,
Activate,
}
#[derive(Clone, Copy, Debug)]
pub enum HeaderToolbarInlineEditorAction {
Chip(ChipConfiguratorAction),
ResetDefault,
Activate,
}
fn open_toolbar_items_from_settings<V: View>(
chip_configurator: &mut ChipConfigurator,
ctx: &mut ViewContext<V>,
) {
let selection = TabSettings::as_ref(ctx)
.header_toolbar_chip_selection
.clone();
open_toolbar_items(
chip_configurator,
selection.left_items(),
selection.right_items(),
ctx,
);
}
fn open_toolbar_items<V: View>(
chip_configurator: &mut ChipConfigurator,
current_left: Vec<HeaderToolbarItemKind>,
current_right: Vec<HeaderToolbarItemKind>,
ctx: &mut ViewContext<V>,
) {
let used_set: Vec<HeaderToolbarItemKind> = current_left
.iter()
.chain(current_right.iter())
.cloned()
.collect();
chip_configurator.reset();
chip_configurator.left_chips = current_left
.into_iter()
.filter(|kind| kind.is_supported(ctx))
.map(|kind| build_configurable_item(&kind))
.collect();
chip_configurator.right_chips = current_right
.into_iter()
.filter(|kind| kind.is_supported(ctx))
.map(|kind| build_configurable_item(&kind))
.collect();
chip_configurator.unused_chips = HeaderToolbarItemKind::all_items()
.into_iter()
.filter(|kind| !used_set.contains(kind) && kind.is_supported(ctx))
.map(|kind| build_configurable_item(&kind))
.collect();
}
fn open_default_toolbar_items<V: View>(
chip_configurator: &mut ChipConfigurator,
ctx: &mut ViewContext<V>,
) {
open_toolbar_items(
chip_configurator,
HeaderToolbarItemKind::default_left(),
HeaderToolbarItemKind::default_right(),
ctx,
);
}
fn current_toolbar_items(
chip_configurator: &ChipConfigurator,
) -> (Vec<HeaderToolbarItemKind>, Vec<HeaderToolbarItemKind>) {
let left = chip_configurator
.left_chips
.iter()
.filter_map(header_toolbar_item_kind)
.collect();
let right = chip_configurator
.right_chips
.iter()
.filter_map(header_toolbar_item_kind)
.collect();
(left, right)
}
fn toolbar_items_match_defaults(
left: &[HeaderToolbarItemKind],
right: &[HeaderToolbarItemKind],
) -> bool {
left == HeaderToolbarItemKind::default_left() && right == HeaderToolbarItemKind::default_right()
}
fn is_toolbar_editor_at_defaults(chip_configurator: &ChipConfigurator) -> bool {
let (left, right) = current_toolbar_items(chip_configurator);
toolbar_items_match_defaults(&left, &right)
}
fn save_toolbar_selection<V: View>(
left: Vec<HeaderToolbarItemKind>,
right: Vec<HeaderToolbarItemKind>,
ctx: &mut ViewContext<V>,
) {
sync_show_hide_settings(&left, &right, ctx);
let selection = if toolbar_items_match_defaults(&left, &right) {
HeaderToolbarChipSelection::Default
} else {
HeaderToolbarChipSelection::Custom { left, right }
};
TabSettings::handle(ctx).update(ctx, |settings, ctx| {
report_if_error!(settings
.header_toolbar_chip_selection
.set_value(selection, ctx));
});
}
fn sync_show_hide_settings<V: View>(
left: &[HeaderToolbarItemKind],
right: &[HeaderToolbarItemKind],
ctx: &mut ViewContext<V>,
) {
let placed: Vec<&HeaderToolbarItemKind> = left.iter().chain(right.iter()).collect();
let code_review_placed = placed.contains(&&HeaderToolbarItemKind::CodeReview);
if *TabSettings::as_ref(ctx).show_code_review_button.value() != code_review_placed {
TabSettings::handle(ctx).update(ctx, |settings, ctx| {
report_if_error!(settings
.show_code_review_button
.set_value(code_review_placed, ctx));
});
}
}
impl HeaderToolbarInlineEditor {
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
let mut editor = Self {
mouse_handles: Default::default(),
chip_configurator: ChipConfigurator::new(ChipConfiguratorLayout::LeftRightZones),
};
editor.reset_from_settings(ctx);
ctx.subscribe_to_model(&TabSettings::handle(ctx), |me, _, event, ctx| {
if matches!(
event,
TabSettingsChangedEvent::HeaderToolbarChipSelection { .. }
) && me.chip_configurator.current_dragging_state.is_none()
{
me.reset_from_settings(ctx);
ctx.notify();
}
});
editor
}
fn reset_from_settings(&mut self, ctx: &mut ViewContext<Self>) {
open_toolbar_items_from_settings(&mut self.chip_configurator, ctx);
}
fn save_current_selection(&self, ctx: &mut ViewContext<Self>) {
let (left, right) = current_toolbar_items(&self.chip_configurator);
save_toolbar_selection(left, right, ctx);
}
}
impl Entity for HeaderToolbarInlineEditor {
type Event = ();
}
impl TypedActionView for HeaderToolbarInlineEditor {
type Action = HeaderToolbarInlineEditorAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
Self::Action::Chip(chip_action) => {
let should_save = self.chip_configurator.handle_action(chip_action, ctx);
if should_save {
self.save_current_selection(ctx);
}
ctx.notify();
}
Self::Action::ResetDefault => {
open_default_toolbar_items(&mut self.chip_configurator, ctx);
self.save_current_selection(ctx);
ctx.notify();
}
Self::Action::Activate => {}
}
}
}
impl View for HeaderToolbarInlineEditor {
fn ui_name() -> &'static str {
"HeaderToolbarInlineEditor"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
render_chip_editor_sections(
&self.chip_configurator,
ChipEditorSectionsConfig {
available_section_label: "Available items",
is_at_defaults: is_toolbar_editor_at_defaults(&self.chip_configurator),
reset_action: HeaderToolbarInlineEditorAction::ResetDefault,
activate_action: HeaderToolbarInlineEditorAction::Activate,
chip_action_wrapper: HeaderToolbarInlineEditorAction::Chip,
mouse_handles: &self.mouse_handles,
},
appearance,
)
}
}
impl HeaderToolbarEditorModal {
pub fn new(_ctx: &mut ViewContext<Self>) -> Self {
Self {
mouse_handles: Default::default(),
chip_configurator: ChipConfigurator::new(ChipConfiguratorLayout::LeftRightZones),
is_dirty: false,
}
}
pub fn open(&mut self, ctx: &mut ViewContext<Self>) {
self.reset();
open_toolbar_items_from_settings(&mut self.chip_configurator, ctx);
ctx.notify();
}
fn save_to_settings(&mut self, ctx: &mut ViewContext<Self>) {
if !self.is_dirty {
return;
}
let (left, right) = current_toolbar_items(&self.chip_configurator);
save_toolbar_selection(left, right, ctx);
}
fn reset(&mut self) {
self.chip_configurator.reset();
self.is_dirty = false;
}
}
impl Entity for HeaderToolbarEditorModal {
type Event = HeaderToolbarEditorEvent;
}
impl TypedActionView for HeaderToolbarEditorModal {
type Action = HeaderToolbarEditorAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
Self::Action::Cancel => {
self.reset();
ctx.emit(HeaderToolbarEditorEvent::Close);
}
Self::Action::Save => {
self.save_to_settings(ctx);
ctx.emit(HeaderToolbarEditorEvent::Close);
}
Self::Action::Chip(chip_action) => {
let mutated = self.chip_configurator.handle_action(chip_action, ctx);
if mutated {
self.is_dirty = true;
}
ctx.notify();
}
Self::Action::ResetDefault => {
self.is_dirty = true;
open_default_toolbar_items(&mut self.chip_configurator, ctx);
ctx.notify();
}
Self::Action::Activate => {}
}
}
}
impl HeaderToolbarEditorModal {
fn is_at_defaults(&self) -> bool {
is_toolbar_editor_at_defaults(&self.chip_configurator)
}
}
impl View for HeaderToolbarEditorModal {
fn ui_name() -> &'static str {
"HeaderToolbarEditorModal"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
render_chip_editor_modal(
&self.chip_configurator,
ChipEditorModalConfig {
title: MODAL_TITLE,
available_section_label: "Available items",
is_at_defaults: self.is_at_defaults(),
is_dirty: self.is_dirty,
cancel_action: HeaderToolbarEditorAction::Cancel,
save_action: HeaderToolbarEditorAction::Save,
reset_action: HeaderToolbarEditorAction::ResetDefault,
activate_action: HeaderToolbarEditorAction::Activate,
chip_action_wrapper: HeaderToolbarEditorAction::Chip,
mouse_handles: &self.mouse_handles,
},
appearance,
)
}
}
fn build_configurable_item(kind: &HeaderToolbarItemKind) -> ConfigurableItem {
let id = serde_json::to_string(kind).expect("HeaderToolbarItemKind is serializable");
let renderer =
ControlItemRenderer::new_with_label_and_icon(kind.display_label().to_string(), kind.icon())
.with_identifier(id);
let renderer = match kind {
HeaderToolbarItemKind::TabsPanel => renderer.non_removable(),
_ => renderer,
};
ConfigurableItem::Control(renderer)
}
fn header_toolbar_item_kind(item: &ConfigurableItem) -> Option<HeaderToolbarItemKind> {
match item {
ConfigurableItem::Control(renderer) => {
let id = renderer.identifier()?;
serde_json::from_str(id).ok()
}
ConfigurableItem::ContextChip(_) => None,
}
}