use super::{Menu, SubMenu, SubMenuBuilder};
use kas::event::{FocusSource, TimerHandle};
use kas::layout::{self, RowPositionSolver, RowSetter, RowSolver, RulesSetter, RulesSolver};
use kas::prelude::*;
use kas::theme::FrameStyle;
const TIMER_SHOW: TimerHandle = TimerHandle::new(0, false);
#[impl_self]
mod MenuBar {
#[widget]
pub struct MenuBar<Data, D: Directional = kas::dir::Right> {
core: widget_core!(),
direction: D,
widgets: Vec<SubMenu<true, Data>>,
layout_store: layout::DynRowStorage,
delayed_open: Option<Id>,
}
impl Self
where
D: Default,
{
pub fn new(menus: Vec<SubMenu<true, Data>>) -> Self {
MenuBar::new_dir(menus, Default::default())
}
pub fn builder() -> MenuBuilder<Data, D> {
MenuBuilder {
menus: vec![],
direction: D::default(),
}
}
}
impl<Data> MenuBar<Data, kas::dir::Right> {
pub fn right(menus: Vec<SubMenu<true, Data>>) -> Self {
MenuBar::new(menus)
}
}
impl Self {
pub fn new_dir(menus: Vec<SubMenu<true, Data>>, direction: D) -> Self {
MenuBar {
core: Default::default(),
direction,
widgets: menus,
layout_store: Default::default(),
delayed_open: None,
}
}
}
impl Layout for Self {
fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
let len = self.widgets.len();
let dim = (self.direction, len + 1);
let mut solver = RowSolver::new(axis, dim, &mut self.layout_store);
let frame_rules = cx.frame(FrameStyle::MenuEntry, axis);
for (n, child) in self.widgets.iter_mut().enumerate() {
solver.for_child(&mut self.layout_store, n, |axis| {
let rules = child.size_rules(cx, axis);
frame_rules.surround(rules).0
});
}
solver.for_child(&mut self.layout_store, len, |axis| {
let mut rules = SizeRules::EMPTY;
if axis.is_horizontal() {
rules.set_stretch(Stretch::Maximize);
}
rules
});
solver.finish(&mut self.layout_store)
}
fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, _: AlignHints) {
self.core.set_rect(rect);
let dim = (self.direction, self.widgets.len() + 1);
let mut setter = RowSetter::<D, Vec<i32>, _>::new(rect, dim, &mut self.layout_store);
let hints = AlignHints::CENTER;
for (n, child) in self.widgets.iter_mut().enumerate() {
child.set_rect(cx, setter.child_rect(&mut self.layout_store, n), hints);
}
}
fn draw(&self, mut draw: DrawCx) {
let solver = RowPositionSolver::new(self.direction);
let rect = self.rect();
solver.for_children(&self.widgets, rect, |w| w.draw(draw.re()));
}
}
impl Tile for Self {
fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
Role::MenuBar
}
#[inline]
fn child_indices(&self) -> ChildIndices {
ChildIndices::range(0..self.widgets.len())
}
fn get_child(&self, index: usize) -> Option<&dyn Tile> {
self.widgets.get(index).map(|w| w.as_tile())
}
}
impl Events for Self {
fn probe(&self, coord: Coord) -> Id {
let solver = RowPositionSolver::new(self.direction);
solver
.find_child(&self.widgets, coord)
.and_then(|child| child.try_probe(coord))
.unwrap_or_else(|| self.id())
}
fn handle_event(&mut self, cx: &mut EventCx, data: &Data, event: Event) -> IsUsed {
match event {
Event::Timer(TIMER_SHOW) => {
if let Some(id) = self.delayed_open.clone() {
self.set_menu_path(cx, data, Some(&id), false);
}
Used
}
Event::PressStart(press) => {
if press
.id
.as_ref()
.map(|id| self.is_ancestor_of(id))
.unwrap_or(false)
{
if press.is_primary() {
let any_menu_open = self.widgets.iter().any(|w| w.menu_is_open());
let press_in_the_bar = self.rect().contains(press.coord());
if !press_in_the_bar || !any_menu_open {
press.grab_move(self.id()).complete(cx);
}
cx.set_grab_depress(*press, press.id.clone());
if press_in_the_bar {
if self
.widgets
.iter()
.any(|w| w.eq_id(&press.id) && !w.menu_is_open())
{
self.set_menu_path(cx, data, press.id.as_ref(), false);
} else {
self.set_menu_path(cx, data, None, false);
}
}
}
Used
} else {
self.delayed_open = None;
self.set_menu_path(cx, data, None, false);
Unused
}
}
Event::PointerMove { press } | Event::PressMove { press, .. } => {
cx.set_grab_depress(*press, press.id.clone());
let id = match press.id {
Some(x) => x,
None => return Used,
};
if self.is_strict_ancestor_of(&id) {
if self.rect().contains(press.coord) {
cx.clear_nav_focus();
self.delayed_open = None;
self.set_menu_path(cx, data, Some(&id), false);
} else if id != self.delayed_open {
cx.request_nav_focus(id.clone(), FocusSource::Pointer);
let delay = cx.config().event().menu_delay();
cx.request_timer(self.id(), TIMER_SHOW, delay);
self.delayed_open = Some(id);
}
} else {
self.delayed_open = None;
}
Used
}
Event::PressEnd { press, success, .. } if success => {
let id = match press.id {
Some(x) => x,
None => return Used,
};
if !self.rect().contains(press.coord) {
self.delayed_open = None;
cx.send(id, Command::Activate);
}
Used
}
Event::Command(cmd, _) => {
use Command::{Left, Up};
let is_vert = self.direction.is_vertical();
let reverse = self.direction.is_reversed() ^ matches!(cmd, Left | Up);
match cmd.as_direction().map(|d| d.is_vertical()) {
Some(v) if v == is_vert => {
for i in 0..self.widgets.len() {
if self.widgets[i].menu_is_open() {
let mut j = isize::conv(i);
j = if reverse { j - 1 } else { j + 1 };
j = j.rem_euclid(self.widgets.len().cast());
self.widgets[i].set_menu_path(cx, data, None, true);
let w = &mut self.widgets[usize::conv(j)];
w.set_menu_path(cx, data, Some(&w.id()), true);
break;
}
}
Used
}
Some(_) => {
cx.next_nav_focus(self.id(), reverse, FocusSource::Key);
Used
}
None => Unused,
}
}
_ => Unused,
}
}
}
impl Widget for Self {
type Data = Data;
fn child_node<'n>(&'n mut self, data: &'n Data, index: usize) -> Option<Node<'n>> {
self.widgets.get_mut(index).map(|w| w.as_node(data))
}
}
impl Self {
fn set_menu_path(
&mut self,
cx: &mut EventCx,
data: &Data,
target: Option<&Id>,
set_focus: bool,
) {
log::trace!(
"set_menu_path: self={}, target={target:?}, set_focus={set_focus}",
self.identify()
);
self.delayed_open = None;
for i in 0..self.widgets.len() {
self.widgets[i].set_menu_path(cx, data, target, set_focus);
}
}
}
}
pub struct MenuBuilder<Data, D: Directional> {
menus: Vec<SubMenu<true, Data>>,
direction: D,
}
impl<Data, D: Directional> MenuBuilder<Data, D> {
pub fn menu<F>(mut self, label: impl Into<AccessString>, f: F) -> Self
where
F: FnOnce(SubMenuBuilder<Data>),
{
let mut menu = Vec::new();
f(SubMenuBuilder { menu: &mut menu });
let dir = self.direction.as_direction().flipped();
self.menus.push(SubMenu::new(label, menu, dir));
self
}
pub fn build(self) -> MenuBar<Data, D> {
MenuBar::new_dir(self.menus, self.direction)
}
}