447 lines
11 KiB
Rust
447 lines
11 KiB
Rust
|
use makepad_widgets::makepad_derive_widget::*;
|
||
|
use makepad_widgets::makepad_draw::*;
|
||
|
use makepad_widgets::widget::*;
|
||
|
|
||
|
live_design! {
|
||
|
import makepad_draw::shader::std::*;
|
||
|
import makepad_widgets::theme_desktop_dark::*;
|
||
|
import crate::shared::styles::*;
|
||
|
|
||
|
DrawBg = {{DrawBg}} {
|
||
|
instance color: #4
|
||
|
instance color_selected: #5
|
||
|
instance border_radius: 4.0
|
||
|
|
||
|
fn pixel(self) -> vec4 {
|
||
|
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
|
||
|
sdf.box(
|
||
|
1.0,
|
||
|
1.0,
|
||
|
self.rect_size.x,
|
||
|
self.rect_size.y,
|
||
|
self.border_radius
|
||
|
)
|
||
|
sdf.fill_keep(mix(self.color, self.color_selected, self.hover))
|
||
|
return sdf.result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DrawName = {{DrawName}} {
|
||
|
text_style: <REGULAR_TEXT>{font_size: 9},
|
||
|
|
||
|
fn get_color(self) -> vec4 {
|
||
|
return mix(
|
||
|
mix(
|
||
|
THEME_COLOR_TEXT_DEFAULT,
|
||
|
THEME_COLOR_TEXT_SELECTED,
|
||
|
self.selected
|
||
|
),
|
||
|
THEME_COLOR_TEXT_HOVER,
|
||
|
self.hover
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MenuItem = {{MenuItem}} {
|
||
|
align: {y: 0.5},
|
||
|
padding: {left: 5., top: 10., bottom: 10., right: 5.},
|
||
|
spacing: 5.,
|
||
|
width: Fill,
|
||
|
height: Fit
|
||
|
|
||
|
icon_walk: {width: 15., height: Fit, margin: {bottom: 3.}}
|
||
|
draw_icon: {
|
||
|
color: #f2f2f2;
|
||
|
brightness: 0.8;
|
||
|
}
|
||
|
|
||
|
animator: {
|
||
|
hover = {
|
||
|
default: off
|
||
|
off = {
|
||
|
from: {all: Snap}
|
||
|
apply: {
|
||
|
draw_bg: {hover: 0.0}
|
||
|
draw_name: {hover: 0.0}
|
||
|
}
|
||
|
}
|
||
|
on = {
|
||
|
cursor: Hand
|
||
|
from: {all: Snap}
|
||
|
apply: {
|
||
|
draw_bg: {hover: 1.0}
|
||
|
draw_name: {hover: 1.0}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
select = {
|
||
|
default: off
|
||
|
off = {
|
||
|
from: {all: Snap}
|
||
|
apply: {
|
||
|
draw_bg: {selected: 0.0,}
|
||
|
draw_name: {selected: 0.0,}
|
||
|
}
|
||
|
}
|
||
|
on = {
|
||
|
from: {all: Snap}
|
||
|
apply: {
|
||
|
draw_bg: {selected: 1.0,}
|
||
|
draw_name: {selected: 1.0,}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
indent_width: 10.0
|
||
|
}
|
||
|
|
||
|
PopupMenu = {{PopupMenu}} {
|
||
|
menu_item: <MenuItem> {}
|
||
|
flow: Down,
|
||
|
padding: 5.,
|
||
|
width: 140.,
|
||
|
height: Fit,
|
||
|
|
||
|
icon_walk: {width: 20., height: Fit}
|
||
|
draw_icon:{
|
||
|
instance hover: 0.0
|
||
|
instance pressed: 0.0
|
||
|
fn get_color(self) -> vec4 {
|
||
|
return mix(
|
||
|
mix(
|
||
|
#9,
|
||
|
#c,
|
||
|
self.hover
|
||
|
),
|
||
|
#9,
|
||
|
self.pressed
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
draw_bg: {
|
||
|
instance color: #4
|
||
|
instance border_width: 0.0,
|
||
|
instance border_color: #4,
|
||
|
instance border_radius: 4.0
|
||
|
|
||
|
fn get_color(self) -> vec4 {
|
||
|
return self.color
|
||
|
}
|
||
|
|
||
|
fn get_border_color(self) -> vec4 {
|
||
|
return self.border_color
|
||
|
}
|
||
|
|
||
|
fn pixel(self) -> vec4 {
|
||
|
|
||
|
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
|
||
|
// sdf.blur = 18.0;
|
||
|
sdf.box(
|
||
|
self.border_width,
|
||
|
self.border_width,
|
||
|
self.rect_size.x - (self.border_width * 2.0),
|
||
|
self.rect_size.y - (self.border_width * 2.0),
|
||
|
self.border_radius
|
||
|
)
|
||
|
sdf.fill_keep(self.get_color())
|
||
|
return sdf.result;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Live, LiveHook)]
|
||
|
#[repr(C)]
|
||
|
struct DrawBg {
|
||
|
#[deref]
|
||
|
draw_super: DrawQuad,
|
||
|
#[live]
|
||
|
selected: f32,
|
||
|
#[live]
|
||
|
hover: f32,
|
||
|
}
|
||
|
|
||
|
#[derive(Live, LiveHook)]
|
||
|
#[repr(C)]
|
||
|
struct DrawName {
|
||
|
#[deref]
|
||
|
draw_super: DrawText,
|
||
|
#[live]
|
||
|
selected: f32,
|
||
|
#[live]
|
||
|
hover: f32,
|
||
|
}
|
||
|
|
||
|
#[derive(Live)]
|
||
|
pub struct PopupMenu {
|
||
|
#[live]
|
||
|
draw_list: DrawList2d,
|
||
|
|
||
|
#[live]
|
||
|
menu_item: Option<LivePtr>,
|
||
|
|
||
|
#[live]
|
||
|
draw_bg: DrawQuad,
|
||
|
#[live]
|
||
|
draw_icon: DrawIcon,
|
||
|
#[live]
|
||
|
icon_walk: Walk,
|
||
|
|
||
|
#[layout]
|
||
|
layout: Layout,
|
||
|
#[walk]
|
||
|
walk: Walk,
|
||
|
|
||
|
#[live]
|
||
|
labels: Vec<String>,
|
||
|
#[live]
|
||
|
values: Vec<LiveValue>,
|
||
|
|
||
|
#[live]
|
||
|
items: Vec<String>,
|
||
|
#[rust]
|
||
|
menu_items: ComponentMap<MenuItemId, MenuItem>,
|
||
|
#[rust]
|
||
|
init_select_item: Option<MenuItemId>,
|
||
|
|
||
|
#[rust]
|
||
|
first_tap: bool,
|
||
|
#[rust]
|
||
|
count: usize,
|
||
|
}
|
||
|
|
||
|
impl LiveHook for PopupMenu {
|
||
|
fn before_live_design(cx: &mut Cx) {
|
||
|
register_widget!(cx, PopupMenu)
|
||
|
}
|
||
|
|
||
|
fn after_apply(&mut self, cx: &mut Cx, from: ApplyFrom, index: usize, nodes: &[LiveNode]) {
|
||
|
if let Some(index) = nodes.child_by_name(index, live_id!(list_node).as_field()) {
|
||
|
for (_, node) in self.menu_items.iter_mut() {
|
||
|
node.apply(cx, from, index, nodes);
|
||
|
}
|
||
|
}
|
||
|
self.draw_list.redraw(cx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Widget for PopupMenu {
|
||
|
fn handle_widget_event_with(
|
||
|
&mut self,
|
||
|
_cx: &mut Cx,
|
||
|
_event: &Event,
|
||
|
_dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem),
|
||
|
) {
|
||
|
}
|
||
|
|
||
|
fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
|
||
|
self.draw_bg.begin(cx, walk, self.layout);
|
||
|
self.draw_icon.draw_walk(cx, self.icon_walk);
|
||
|
self.draw_bg.end(cx);
|
||
|
WidgetDraw::done()
|
||
|
}
|
||
|
|
||
|
fn walk(&mut self, _cx: &mut Cx) -> Walk {
|
||
|
self.walk
|
||
|
}
|
||
|
|
||
|
fn redraw(&mut self, cx: &mut Cx) {
|
||
|
self.draw_bg.redraw(cx);
|
||
|
self.draw_icon.redraw(cx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl PopupMenu {
|
||
|
pub fn menu_contains_pos(&self, cx: &mut Cx, pos: DVec2) -> bool {
|
||
|
self.draw_bg.area().get_clipped_rect(cx).contains(pos)
|
||
|
}
|
||
|
|
||
|
pub fn begin(&mut self, cx: &mut Cx2d) {
|
||
|
self.draw_list.begin_overlay_reuse(cx);
|
||
|
|
||
|
cx.begin_pass_sized_turtle(Layout::flow_down());
|
||
|
|
||
|
self.draw_bg.begin(cx, self.walk, self.layout);
|
||
|
self.count = 0;
|
||
|
}
|
||
|
|
||
|
pub fn end(&mut self, cx: &mut Cx2d, shift_area: Area) {
|
||
|
self.draw_bg.end(cx);
|
||
|
let area = self.draw_bg.area().get_rect(cx);
|
||
|
let shift = DVec2 {
|
||
|
x: -area.size.x + (shift_area.get_rect(cx).size.x * 0.7),
|
||
|
y: 30.,
|
||
|
};
|
||
|
|
||
|
cx.end_pass_sized_turtle_with_shift(shift_area, shift);
|
||
|
self.draw_list.end(cx);
|
||
|
|
||
|
self.menu_items.retain_visible();
|
||
|
if let Some(init_select_item) = self.init_select_item.take() {
|
||
|
self.select_item_state(cx, init_select_item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn draw_item(
|
||
|
&mut self,
|
||
|
cx: &mut Cx2d,
|
||
|
item_id: MenuItemId,
|
||
|
label: &str,
|
||
|
icon: LiveDependency,
|
||
|
) {
|
||
|
self.count += 1;
|
||
|
|
||
|
let menu_item = self.menu_item;
|
||
|
let menu_item = self
|
||
|
.menu_items
|
||
|
.get_or_insert(cx, item_id, |cx| MenuItem::new_from_ptr(cx, menu_item));
|
||
|
menu_item.draw_item(cx, label, icon);
|
||
|
}
|
||
|
|
||
|
pub fn init_select_item(&mut self, which_id: MenuItemId) {
|
||
|
self.init_select_item = Some(which_id);
|
||
|
self.first_tap = true;
|
||
|
}
|
||
|
|
||
|
fn select_item_state(&mut self, cx: &mut Cx, which_id: MenuItemId) {
|
||
|
for (id, item) in &mut *self.menu_items {
|
||
|
if *id == which_id {
|
||
|
item.animator_cut(cx, id!(select.on));
|
||
|
item.animator_cut(cx, id!(hover.on));
|
||
|
} else {
|
||
|
item.animator_cut(cx, id!(select.off));
|
||
|
item.animator_cut(cx, id!(hover.off));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn handle_event_with(
|
||
|
&mut self,
|
||
|
cx: &mut Cx,
|
||
|
event: &Event,
|
||
|
sweep_area: Area,
|
||
|
dispatch_action: &mut dyn FnMut(&mut Cx, PopupMenuAction),
|
||
|
) {
|
||
|
let mut actions = Vec::new();
|
||
|
for (item_id, node) in self.menu_items.iter_mut() {
|
||
|
node.handle_event_with(cx, event, sweep_area, &mut |_, e| {
|
||
|
actions.push((*item_id, e))
|
||
|
});
|
||
|
}
|
||
|
|
||
|
for (node_id, action) in actions {
|
||
|
match action {
|
||
|
MenuItemAction::WasSweeped => {
|
||
|
self.select_item_state(cx, node_id);
|
||
|
dispatch_action(cx, PopupMenuAction::WasSweeped(node_id));
|
||
|
}
|
||
|
MenuItemAction::WasSelected => {
|
||
|
self.select_item_state(cx, node_id);
|
||
|
dispatch_action(cx, PopupMenuAction::WasSelected(node_id));
|
||
|
}
|
||
|
_ => (),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Live, LiveHook)]
|
||
|
pub struct MenuItem {
|
||
|
#[layout]
|
||
|
layout: Layout,
|
||
|
#[walk]
|
||
|
walk: Walk,
|
||
|
|
||
|
#[live]
|
||
|
draw_bg: DrawBg,
|
||
|
#[live]
|
||
|
draw_name: DrawName,
|
||
|
|
||
|
#[live]
|
||
|
draw_icon: DrawIcon,
|
||
|
#[live]
|
||
|
icon_walk: Walk,
|
||
|
|
||
|
#[live]
|
||
|
indent_width: f32,
|
||
|
|
||
|
#[animator]
|
||
|
animator: Animator,
|
||
|
#[live]
|
||
|
opened: f32,
|
||
|
#[live]
|
||
|
hover: f32,
|
||
|
#[live]
|
||
|
selected: f32,
|
||
|
}
|
||
|
|
||
|
#[derive(Default, Clone)]
|
||
|
pub enum MenuItemAction {
|
||
|
WasSweeped,
|
||
|
WasSelected,
|
||
|
#[default]
|
||
|
None,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, WidgetAction, Debug)]
|
||
|
pub enum PopupMenuAction {
|
||
|
WasSweeped(MenuItemId),
|
||
|
WasSelected(MenuItemId),
|
||
|
None,
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Debug, Default, Eq, Hash, Copy, PartialEq, FromLiveId)]
|
||
|
pub struct MenuItemId(pub LiveId);
|
||
|
|
||
|
impl MenuItem {
|
||
|
pub fn draw_item(&mut self, cx: &mut Cx2d, label: &str, icon: LiveDependency) {
|
||
|
self.draw_bg.begin(cx, self.walk, self.layout);
|
||
|
self.draw_icon.svg_file = icon;
|
||
|
self.draw_icon.draw_walk(cx, self.icon_walk);
|
||
|
self.draw_name
|
||
|
.draw_walk(cx, Walk::fit(), Align { x: 0., y: 0.5 }, label);
|
||
|
self.draw_bg.end(cx);
|
||
|
}
|
||
|
|
||
|
pub fn handle_event_with(
|
||
|
&mut self,
|
||
|
cx: &mut Cx,
|
||
|
event: &Event,
|
||
|
sweep_area: Area,
|
||
|
dispatch_action: &mut dyn FnMut(&mut Cx, MenuItemAction),
|
||
|
) {
|
||
|
if self.animator_handle_event(cx, event).must_redraw() {
|
||
|
self.draw_bg.area().redraw(cx);
|
||
|
}
|
||
|
|
||
|
match event.hits_with_options(
|
||
|
cx,
|
||
|
self.draw_bg.area(),
|
||
|
HitOptions::new().with_sweep_area(sweep_area),
|
||
|
) {
|
||
|
Hit::FingerHoverIn(_) => {
|
||
|
self.animator_play(cx, id!(hover.on));
|
||
|
}
|
||
|
Hit::FingerHoverOut(_) => {
|
||
|
self.animator_play(cx, id!(hover.off));
|
||
|
}
|
||
|
Hit::FingerDown(_) => {
|
||
|
dispatch_action(cx, MenuItemAction::WasSweeped);
|
||
|
self.animator_play(cx, id!(hover.on));
|
||
|
self.animator_play(cx, id!(select.on));
|
||
|
}
|
||
|
Hit::FingerUp(se) => {
|
||
|
if !se.is_sweep {
|
||
|
dispatch_action(cx, MenuItemAction::WasSelected);
|
||
|
} else {
|
||
|
self.animator_play(cx, id!(hover.off));
|
||
|
self.animator_play(cx, id!(select.off));
|
||
|
}
|
||
|
}
|
||
|
_ => {}
|
||
|
}
|
||
|
}
|
||
|
}
|