use teksilo_i18n::{LocalizedString, lit};
use crate::menu_item::MenuItem;
use crate::menu_list::MenuList;
use super::geometry::DockSide;
use super::model::{DockOpenLocation, DockTabId, DockTabView, DockWidgetId, DockingModel};
#[derive(Clone, Copy)]
pub(crate) enum DockMenuKind {
Rail,
Strip,
}
fn side_name(side: DockSide) -> LocalizedString {
match side {
DockSide::Leading => lit!("Leading"),
DockSide::Trailing => lit!("Trailing"),
DockSide::Top => lit!("Top"),
DockSide::Bottom => lit!("Bottom"),
}
}
pub(crate) fn activity_context_menu(
model: &DockingModel,
side: DockSide,
tab_id: DockTabId,
kind: DockMenuKind,
) -> MenuList {
let tabs = model.side_tabs(side);
let this_label = tabs
.iter()
.find(|v| v.id == tab_id)
.map(|v| model.activity_label(v))
.unwrap_or_else(|| lit!("Panel"));
let policy = model.policy();
let mut list = MenuList::new();
let mut has_items = false;
if policy.allow_activity_hide {
let m = model.clone();
let hide_label = lit!(format!("Hide \"{}\"", this_label.resolve_now()));
list = list.item(
MenuItem::new(hide_label).on_activate_fn(move |_| m.set_tab_hidden(tab_id, true)),
);
has_items = true;
}
if policy.allow_activity_drag && !model.enabled_move_targets(side).is_empty() {
if has_items {
list = list.separator();
}
let m = model.clone();
list = list.item(MenuItem::submenu(lit!("Move to"), move || {
Box::new(move_to_submenu(&m, side, tab_id))
}));
has_items = true;
}
if policy.allow_activity_hide {
if has_items {
list = list.separator();
}
list = activity_checkitems(list, model, side, &tabs);
has_items = true;
}
if has_items {
list = list.separator();
}
list = match kind {
DockMenuKind::Rail => {
let m = model.clone();
list.item(MenuItem::submenu(lit!("Activity bar size"), move || {
Box::new(rail_size_submenu(&m, side))
}))
}
DockMenuKind::Strip => {
let m = model.clone();
list.item(MenuItem::submenu(lit!("Tab size"), move || {
Box::new(tab_size_submenu(&m, side))
}))
}
};
list
}
pub(crate) fn dock_options_menu(
model: &DockingModel,
side: DockSide,
tab_id: DockTabId,
dock_id: DockWidgetId,
multi_pane: bool,
) -> MenuList {
let policy = model.policy();
let has_targets = !model.enabled_move_targets(side).is_empty();
let mut list = MenuList::new();
if multi_pane {
if policy.allow_activity_drag {
let m = model.clone();
list = list.item(MenuItem::new(lit!("Move to new activity")).on_activate_fn(
move |_| {
let at = m.side_append_index(side);
m.promote_to_tab(dock_id, side, at);
},
));
if has_targets {
let m = model.clone();
list = list.item(MenuItem::submenu(lit!("Move to side"), move || {
Box::new(dock_move_to_submenu(&m, dock_id, side))
}));
}
}
} else {
let mut has_items = false;
if policy.allow_activity_hide {
let this_label = model
.side_tabs(side)
.iter()
.find(|v| v.id == tab_id)
.map(|v| model.activity_label(v))
.unwrap_or_else(|| lit!("Panel"));
let m = model.clone();
let hide_label = lit!(format!("Hide \"{}\"", this_label.resolve_now()));
list = list.item(
MenuItem::new(hide_label).on_activate_fn(move |_| m.set_tab_hidden(tab_id, true)),
);
has_items = true;
}
if policy.allow_activity_drag && has_targets {
if has_items {
list = list.separator();
}
let m = model.clone();
list = list.item(MenuItem::submenu(lit!("Move to"), move || {
Box::new(move_to_submenu(&m, side, tab_id))
}));
}
}
list
}
pub(crate) fn dock_has_options(model: &DockingModel, side: DockSide, multi_pane: bool) -> bool {
let policy = model.policy();
if multi_pane {
policy.allow_activity_drag
} else {
policy.allow_activity_hide
|| (policy.allow_activity_drag && !model.enabled_move_targets(side).is_empty())
}
}
pub(crate) fn background_menu(
model: &DockingModel,
side: DockSide,
kind: DockMenuKind,
) -> MenuList {
let tabs = model.side_tabs(side);
let mut list = MenuList::new();
if model.policy().allow_activity_hide {
list = activity_checkitems(list, model, side, &tabs);
list = list.separator();
}
let m = model.clone();
match kind {
DockMenuKind::Rail => list.item(MenuItem::submenu(lit!("Activity bar size"), move || {
Box::new(rail_size_submenu(&m, side))
})),
DockMenuKind::Strip => list.item(MenuItem::submenu(lit!("Tab size"), move || {
Box::new(tab_size_submenu(&m, side))
})),
}
}
fn activity_checkitems(
mut list: MenuList,
model: &DockingModel,
_side: DockSide,
tabs: &[DockTabView],
) -> MenuList {
for view in tabs {
let tab_id = view.id;
let label = model.activity_label(view);
let visible_sig = model.tab_hidden_signal(tab_id).map(|h| !*h);
let m = model.clone();
list = list.item(
MenuItem::new(label)
.reflect_checked(visible_sig)
.on_activate_fn(move |_| m.set_tab_hidden(tab_id, !m.is_tab_hidden(tab_id))),
);
}
list
}
fn move_to_submenu(model: &DockingModel, from: DockSide, tab_id: DockTabId) -> MenuList {
let mut list = MenuList::new();
for target in model.enabled_move_targets(from) {
let m = model.clone();
list = list.item(MenuItem::new(side_name(target)).on_activate_fn(move |_| {
let at = m.side_append_index(target);
m.move_tab(tab_id, target, at);
m.set_side_visible(target, true);
m.select_tab_by_id(target, tab_id);
}));
}
list
}
fn dock_move_to_submenu(model: &DockingModel, dock_id: DockWidgetId, from: DockSide) -> MenuList {
let mut list = MenuList::new();
for target in model.enabled_move_targets(from) {
let m = model.clone();
list = list.item(MenuItem::new(side_name(target)).on_activate_fn(move |_| {
m.move_dock(dock_id, DockOpenLocation::side(target).new_tab());
m.set_side_visible(target, true);
}));
}
list
}
fn rail_size_submenu(model: &DockingModel, side: DockSide) -> MenuList {
let sig = model.rail_size_signal(side);
MenuList::new()
.item(MenuItem::new(lit!("Default")).radio(0, sig.clone()))
.item(MenuItem::new(lit!("Compact")).radio(1, sig.clone()))
.item(MenuItem::new(lit!("Icon + Label")).radio(2, sig))
}
fn tab_size_submenu(model: &DockingModel, side: DockSide) -> MenuList {
let sig = model.tab_display_signal(side);
MenuList::new()
.item(MenuItem::new(lit!("Text")).radio(0, sig.clone()))
.item(MenuItem::new(lit!("Icon")).radio(1, sig.clone()))
.item(MenuItem::new(lit!("Icon + Text")).radio(2, sig))
}
#[cfg(test)]
mod tests {
use super::super::geometry::DockSide;
use super::super::model::{
DockOpenLocation, DockPolicy, DockWidgetId, DockWidgetMeta, DockingModel,
};
use super::dock_has_options;
use teksilo_i18n::lit;
fn open(m: &DockingModel) {
let id = DockWidgetId::fresh();
m.register_meta(
id,
DockWidgetMeta {
title: lit!("Dock"),
icon: None,
min_size: None,
default: DockOpenLocation::side(DockSide::Leading),
header_actions: None,
show_header: false,
},
);
m.open_dock(id, DockOpenLocation::side(DockSide::Leading));
}
#[test]
fn dock_has_options_under_default_policy() {
let m = DockingModel::new();
open(&m);
assert!(dock_has_options(&m, DockSide::Leading, true));
assert!(dock_has_options(&m, DockSide::Leading, false));
}
#[test]
fn locked_dock_has_no_options() {
let m = DockingModel::new();
open(&m);
m.set_policy(DockPolicy::locked());
assert!(!dock_has_options(&m, DockSide::Leading, true));
assert!(!dock_has_options(&m, DockSide::Leading, false));
}
}