390 lines
12 KiB
Rust
390 lines
12 KiB
Rust
|
use std::cell::RefCell;
|
||
|
use std::rc::Rc;
|
||
|
|
||
|
use makepad_widgets::makepad_derive_widget::*;
|
||
|
use makepad_widgets::makepad_draw::*;
|
||
|
use makepad_widgets::widget::*;
|
||
|
|
||
|
use crate::shared::popup_menu::*;
|
||
|
|
||
|
live_design! {
|
||
|
import makepad_draw::shader::std::*;
|
||
|
import makepad_widgets::theme_desktop_dark::*;
|
||
|
import crate::shared::popup_menu::*;
|
||
|
|
||
|
DropDown = {{WechatDropDown}} {
|
||
|
width: Fit,
|
||
|
height: Fit,
|
||
|
margin: {left: 1.0, right: 1.0, top: 1.0, bottom: 1.0},
|
||
|
align: {x: 0., y: 0.},
|
||
|
padding: {left: 5.0, top: 5.0, right: 4.0, bottom: 5.0}
|
||
|
|
||
|
draw_bg: {
|
||
|
fn pixel(self) -> vec4 {
|
||
|
let sdf = Sdf2d::viewport(self.pos * self.rect_size);
|
||
|
return sdf.result
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
icon_walk: { width: 20., height: Fit}
|
||
|
|
||
|
popup_menu: <PopupMenu> {
|
||
|
}
|
||
|
|
||
|
popup_shift: vec2(-6.0,4.0)
|
||
|
|
||
|
selected_item: -1
|
||
|
animator: {
|
||
|
hover = {
|
||
|
default: off,
|
||
|
off = {
|
||
|
from: {all: Forward {duration: 0.1}}
|
||
|
apply: {
|
||
|
draw_icon: {pressed: 0.0, hover: 0.0}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
on = {
|
||
|
from: {
|
||
|
all: Forward {duration: 0.1}
|
||
|
pressed: Forward {duration: 0.01}
|
||
|
}
|
||
|
apply: {
|
||
|
draw_icon: {pressed: 0.0, hover: [{time: 0.0, value: 1.0}],}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pressed = {
|
||
|
from: {all: Forward {duration: 0.2}}
|
||
|
apply: {
|
||
|
draw_icon: {pressed: [{time: 0.0, value: 1.0}], hover: 1.0,}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Live)]
|
||
|
pub struct WechatDropDown {
|
||
|
#[animator]
|
||
|
animator: Animator,
|
||
|
|
||
|
#[walk]
|
||
|
walk: Walk,
|
||
|
#[layout]
|
||
|
layout: Layout,
|
||
|
|
||
|
#[live]
|
||
|
draw_bg: DrawQuad,
|
||
|
#[live]
|
||
|
draw_icon: DrawIcon,
|
||
|
#[live]
|
||
|
icon_walk: Walk,
|
||
|
|
||
|
#[live]
|
||
|
bind: String,
|
||
|
#[live]
|
||
|
bind_enum: String,
|
||
|
|
||
|
#[live]
|
||
|
popup_menu: Option<LivePtr>,
|
||
|
|
||
|
#[live]
|
||
|
labels: Vec<String>,
|
||
|
#[live]
|
||
|
values: Vec<LiveValue>,
|
||
|
#[live]
|
||
|
icons: Vec<LiveDependency>,
|
||
|
|
||
|
#[live]
|
||
|
popup_shift: DVec2,
|
||
|
|
||
|
#[rust]
|
||
|
is_open: bool,
|
||
|
|
||
|
#[live]
|
||
|
selected_item: usize,
|
||
|
}
|
||
|
|
||
|
impl LiveHook for WechatDropDown {
|
||
|
fn before_live_design(cx: &mut Cx) {
|
||
|
register_widget!(cx, WechatDropDown)
|
||
|
}
|
||
|
|
||
|
fn after_apply(&mut self, cx: &mut Cx, from: ApplyFrom, _index: usize, _nodes: &[LiveNode]) {
|
||
|
if self.popup_menu.is_none() || !from.is_from_doc() {
|
||
|
return;
|
||
|
}
|
||
|
let global = cx.global::<PopupMenuGlobal>().clone();
|
||
|
let mut map = global.map.borrow_mut();
|
||
|
|
||
|
// when live styling clean up old style references
|
||
|
map.retain(|k, _| cx.live_registry.borrow().generation_valid(*k));
|
||
|
|
||
|
let list_box = self.popup_menu.unwrap();
|
||
|
map.get_or_insert(cx, list_box, |cx| {
|
||
|
PopupMenu::new_from_ptr(cx, Some(list_box))
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
#[derive(Clone, WidgetAction, Debug)]
|
||
|
pub enum WechatDropDownAction {
|
||
|
Select(usize, LiveValue),
|
||
|
None,
|
||
|
}
|
||
|
|
||
|
#[derive(Default, Clone)]
|
||
|
struct PopupMenuGlobal {
|
||
|
map: Rc<RefCell<ComponentMap<LivePtr, PopupMenu>>>,
|
||
|
}
|
||
|
|
||
|
impl WechatDropDown {
|
||
|
pub fn toggle_open(&mut self, cx: &mut Cx) {
|
||
|
if self.is_open {
|
||
|
self.set_closed(cx);
|
||
|
} else {
|
||
|
self.set_open(cx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn set_open(&mut self, cx: &mut Cx) {
|
||
|
self.is_open = true;
|
||
|
self.draw_bg.redraw(cx);
|
||
|
let global = cx.global::<PopupMenuGlobal>().clone();
|
||
|
let mut map = global.map.borrow_mut();
|
||
|
let lb = map.get_mut(&self.popup_menu.unwrap()).unwrap();
|
||
|
let node_id = LiveId(self.selected_item as u64).into();
|
||
|
lb.init_select_item(node_id);
|
||
|
cx.sweep_lock(self.draw_bg.area());
|
||
|
}
|
||
|
|
||
|
pub fn set_closed(&mut self, cx: &mut Cx) {
|
||
|
self.is_open = false;
|
||
|
self.draw_bg.redraw(cx);
|
||
|
cx.sweep_unlock(self.draw_bg.area());
|
||
|
}
|
||
|
|
||
|
pub fn handle_event_with(
|
||
|
&mut self,
|
||
|
cx: &mut Cx,
|
||
|
event: &Event,
|
||
|
dispatch_action: &mut dyn FnMut(&mut Cx, WechatDropDownAction),
|
||
|
) {
|
||
|
self.animator_handle_event(cx, event);
|
||
|
|
||
|
if self.is_open && self.popup_menu.is_some() {
|
||
|
let global = cx.global::<PopupMenuGlobal>().clone();
|
||
|
let mut map = global.map.borrow_mut();
|
||
|
let menu = map.get_mut(&self.popup_menu.unwrap()).unwrap();
|
||
|
let mut close = false;
|
||
|
menu.handle_event_with(
|
||
|
cx,
|
||
|
event,
|
||
|
self.draw_bg.area(),
|
||
|
&mut |cx, action| if let PopupMenuAction::WasSelected(node_id) = action {
|
||
|
self.selected_item = node_id.0 .0 as usize;
|
||
|
dispatch_action(
|
||
|
cx,
|
||
|
WechatDropDownAction::Select(
|
||
|
self.selected_item,
|
||
|
self.values
|
||
|
.get(self.selected_item)
|
||
|
.cloned()
|
||
|
.unwrap_or(LiveValue::None),
|
||
|
),
|
||
|
);
|
||
|
self.draw_bg.redraw(cx);
|
||
|
close = true;
|
||
|
},
|
||
|
);
|
||
|
if close {
|
||
|
self.set_closed(cx);
|
||
|
}
|
||
|
|
||
|
// check if we touch outside of the popup menu
|
||
|
if let Event::MouseDown(e) = event {
|
||
|
if !menu.menu_contains_pos(cx, e.abs) {
|
||
|
self.set_closed(cx);
|
||
|
self.animator_play(cx, id!(hover.off));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// TODO: close on clicking outside of the popup menu
|
||
|
match event.hits_with_sweep_area(cx, self.draw_bg.area(), self.draw_bg.area()) {
|
||
|
Hit::KeyFocusLost(_) => {
|
||
|
self.set_closed(cx);
|
||
|
self.animator_play(cx, id!(hover.off));
|
||
|
self.draw_bg.redraw(cx);
|
||
|
}
|
||
|
Hit::KeyDown(ke) => match ke.key_code {
|
||
|
KeyCode::ArrowUp => {
|
||
|
if self.selected_item > 0 {
|
||
|
self.selected_item -= 1;
|
||
|
dispatch_action(
|
||
|
cx,
|
||
|
WechatDropDownAction::Select(
|
||
|
self.selected_item,
|
||
|
self.values[self.selected_item].clone(),
|
||
|
),
|
||
|
);
|
||
|
self.set_closed(cx);
|
||
|
self.draw_bg.redraw(cx);
|
||
|
}
|
||
|
}
|
||
|
KeyCode::ArrowDown => {
|
||
|
if !self.values.is_empty() && self.selected_item < self.values.len() - 1 {
|
||
|
self.selected_item += 1;
|
||
|
dispatch_action(
|
||
|
cx,
|
||
|
WechatDropDownAction::Select(
|
||
|
self.selected_item,
|
||
|
self.values[self.selected_item].clone(),
|
||
|
),
|
||
|
);
|
||
|
self.set_closed(cx);
|
||
|
self.draw_bg.redraw(cx);
|
||
|
}
|
||
|
}
|
||
|
_ => (),
|
||
|
},
|
||
|
Hit::FingerDown(_fe) => {
|
||
|
cx.set_key_focus(self.draw_bg.area());
|
||
|
self.toggle_open(cx);
|
||
|
self.animator_play(cx, id!(hover.pressed));
|
||
|
}
|
||
|
Hit::FingerHoverIn(_) => {
|
||
|
cx.set_cursor(MouseCursor::Hand);
|
||
|
self.animator_play(cx, id!(hover.on));
|
||
|
}
|
||
|
Hit::FingerHoverOut(_) => {
|
||
|
self.animator_play(cx, id!(hover.off));
|
||
|
}
|
||
|
Hit::FingerUp(fe) => {
|
||
|
if fe.is_over {
|
||
|
if fe.device.has_hovers() {
|
||
|
self.animator_play(cx, id!(hover.on));
|
||
|
}
|
||
|
} else {
|
||
|
self.animator_play(cx, id!(hover.off));
|
||
|
}
|
||
|
}
|
||
|
_ => (),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn draw_walk(&mut self, cx: &mut Cx2d, walk: Walk) {
|
||
|
// cx.clear_sweep_lock(self.draw_bg.area());
|
||
|
|
||
|
self.draw_bg.begin(cx, walk, self.layout);
|
||
|
//let start_pos = cx.turtle().rect().pos;
|
||
|
self.draw_icon.draw_walk(cx, self.icon_walk);
|
||
|
self.draw_bg.end(cx);
|
||
|
|
||
|
cx.add_nav_stop(self.draw_bg.area(), NavRole::DropDown, Margin::default());
|
||
|
|
||
|
if self.is_open && self.popup_menu.is_some() {
|
||
|
// cx.set_sweep_lock(self.draw_bg.area());
|
||
|
let global = cx.global::<PopupMenuGlobal>().clone();
|
||
|
let mut map = global.map.borrow_mut();
|
||
|
let popup_menu = map.get_mut(&self.popup_menu.unwrap()).unwrap();
|
||
|
|
||
|
popup_menu.begin(cx);
|
||
|
|
||
|
for (i, item) in self.labels.iter().enumerate() {
|
||
|
let node_id = LiveId(i as u64).into();
|
||
|
popup_menu.draw_item(cx, node_id, item, self.icons[i].clone());
|
||
|
}
|
||
|
|
||
|
popup_menu.end(cx, self.draw_bg.area());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// It is named WechatDropDown because DropDown is already a widget in makepad_widgets
|
||
|
impl Widget for WechatDropDown {
|
||
|
fn widget_to_data(
|
||
|
&self,
|
||
|
_cx: &mut Cx,
|
||
|
actions: &WidgetActions,
|
||
|
nodes: &mut LiveNodeVec,
|
||
|
path: &[LiveId],
|
||
|
) -> bool {
|
||
|
match actions.single_action(self.widget_uid()) {
|
||
|
WechatDropDownAction::Select(_, value) => {
|
||
|
nodes.write_field_value(path, value.clone());
|
||
|
true
|
||
|
}
|
||
|
_ => false,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn data_to_widget(&mut self, cx: &mut Cx, nodes: &[LiveNode], path: &[LiveId]) {
|
||
|
if let Some(value) = nodes.read_field_value(path) {
|
||
|
if let Some(index) = self.values.iter().position(|v| v == value) {
|
||
|
if self.selected_item != index {
|
||
|
self.selected_item = index;
|
||
|
self.redraw(cx);
|
||
|
}
|
||
|
} else {
|
||
|
// error!("Value not in values list {:?}", value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn redraw(&mut self, cx: &mut Cx) {
|
||
|
self.draw_bg.redraw(cx);
|
||
|
}
|
||
|
|
||
|
fn handle_widget_event_with(
|
||
|
&mut self,
|
||
|
cx: &mut Cx,
|
||
|
event: &Event,
|
||
|
dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem),
|
||
|
) {
|
||
|
let uid = self.widget_uid();
|
||
|
self.handle_event_with(cx, event, &mut |cx, action| {
|
||
|
dispatch_action(cx, WidgetActionItem::new(action.into(), uid))
|
||
|
});
|
||
|
}
|
||
|
|
||
|
fn walk(&mut self, _cx: &mut Cx) -> Walk {
|
||
|
self.walk
|
||
|
}
|
||
|
|
||
|
fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
|
||
|
self.draw_walk(cx, walk);
|
||
|
WidgetDraw::done()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, PartialEq, WidgetRef)]
|
||
|
pub struct WechatDropDownRef(WidgetRef);
|
||
|
|
||
|
impl WechatDropDownRef {
|
||
|
pub fn item_clicked(&mut self, item_id: &[LiveId], actions: &WidgetActions) -> bool {
|
||
|
if let Some(item) = actions.find_single_action(self.widget_uid()) {
|
||
|
if let WechatDropDownAction::Select(_id, value) = item.action() {
|
||
|
return LiveValue::Bool(true) == value.enum_eq(item_id)
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
}
|