app/menu: add color active/alert status for channels

This commit is contained in:
jkds
2026-01-21 10:49:30 +01:00
parent c80fd29e2e
commit f8c9e963ce
4 changed files with 162 additions and 20 deletions

View File

@@ -22,7 +22,7 @@ DEBUG_FEATURES = --features=enable-filelog,enable-plugins
#DEV_FEATURES = --features=enable-filelog,enable-netdebug,emulate-android
#DEV_FEATURES = --features=enable-filelog,enable-netdebug,enable-plugins
DEV_FEATURES = --features=schema-app
DEV_FEATURES = --features=schema-app,enable-netdebug
default: build-release
./darkfi-app

View File

@@ -649,6 +649,20 @@ pub fn create_menu(name: &str) -> SceneNode {
prop.set_range_f32(0., 1.);
node.add_property(prop).unwrap();
let mut prop = Property::new("active_color", PropertyType::Float32, PropertySubType::Color);
prop.set_ui_text("Active Color", "Active item text color");
prop.set_array_len(4);
prop.set_defaults_f32(vec![1., 1., 1., 1.]).unwrap();
prop.set_range_f32(0., 1.);
node.add_property(prop).unwrap();
let mut prop = Property::new("alert_color", PropertyType::Float32, PropertySubType::Color);
prop.set_ui_text("Alert Color", "Alert item text color");
prop.set_array_len(4);
prop.set_defaults_f32(vec![1., 0.3, 0.3, 1.]).unwrap();
prop.set_range_f32(0., 1.);
node.add_property(prop).unwrap();
let mut prop =
Property::new("scroll_start_accel", PropertyType::Float32, PropertySubType::Null);
prop.set_ui_text("Scroll Start Acceleration", "Multiplier for initial scroll velocity");
@@ -673,5 +687,11 @@ pub fn create_menu(name: &str) -> SceneNode {
)
.unwrap();
node.add_method("mark_active", vec![("item_name", "Item name", CallArgType::Str)], None)
.unwrap();
node.add_method("mark_alert", vec![("item_name", "Item name", CallArgType::Str)], None)
.unwrap();
node
}

View File

@@ -199,6 +199,18 @@ pub async fn make(app: &App, content: SceneNodePtr, i18n_fish: &I18nBabelFish) {
prop.set_f32(atom, Role::App, 2, 1.).unwrap();
prop.set_f32(atom, Role::App, 3, 1.).unwrap();
let prop = node.get_property("active_color").unwrap();
prop.set_f32(atom, Role::App, 0, 0.36).unwrap();
prop.set_f32(atom, Role::App, 1, 1.).unwrap();
prop.set_f32(atom, Role::App, 2, 0.51).unwrap();
prop.set_f32(atom, Role::App, 3, 1.).unwrap();
let prop = node.get_property("alert_color").unwrap();
prop.set_f32(atom, Role::App, 0, 0.56).unwrap();
prop.set_f32(atom, Role::App, 1, 0.61).unwrap();
prop.set_f32(atom, Role::App, 2, 1.).unwrap();
prop.set_f32(atom, Role::App, 3, 1.).unwrap();
let prop = node.get_property("sep_color").unwrap();
prop.set_f32(atom, Role::App, 0, 0.4).unwrap();
prop.set_f32(atom, Role::App, 1, 0.4).unwrap();
@@ -237,7 +249,6 @@ pub async fn make(app: &App, content: SceneNodePtr, i18n_fish: &I18nBabelFish) {
info!(target: "app::menu", "clicked: {channel}!");
chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
menu_is_visible.set(atom, false);
//set_normal_color(atom);
}
});
app.tasks.lock().unwrap().push(listen_click);

View File

@@ -19,15 +19,16 @@
use async_trait::async_trait;
use atomic_float::AtomicF32;
use darkfi::system::CondVar;
use darkfi_serial::serialize;
use darkfi_serial::{serialize, Decodable};
use miniquad::{MouseButton, TouchPhase};
use parking_lot::Mutex as SyncMutex;
use rand::{rngs::OsRng, Rng};
use std::{
collections::VecDeque,
collections::{HashMap, VecDeque},
io::Read,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
Arc, Weak,
},
};
@@ -38,7 +39,7 @@ use crate::{
BatchGuardId, BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor,
PropertyFloat32, PropertyPtr, PropertyRect, PropertyUint32, Role,
},
scene::{Pimpl, SceneNodeWeak},
scene::{MethodCallSub, Pimpl, SceneNodeWeak},
text, ExecutorPtr,
};
@@ -49,6 +50,12 @@ const BIG_EPSILON: f32 = 0.05;
macro_rules! d { ($($arg:tt)*) => { debug!(target: "ui::menu", $($arg)*); } }
#[derive(Clone, Copy, PartialEq, Eq)]
enum ItemStatus {
Active,
Alert,
}
#[derive(Clone)]
struct TouchInfo {
start_scroll: f32,
@@ -109,6 +116,8 @@ pub struct Menu {
bg_color: PropertyColor,
sep_size: PropertyFloat32,
sep_color: PropertyColor,
active_color: PropertyColor,
alert_color: PropertyColor,
window_scale: PropertyFloat32,
mouse_pos: SyncMutex<Point>,
@@ -119,6 +128,7 @@ pub struct Menu {
speed: AtomicF32,
parent_rect: SyncMutex<Option<Rectangle>>,
item_states: SyncMutex<HashMap<String, ItemStatus>>,
}
impl Menu {
@@ -140,6 +150,8 @@ impl Menu {
let bg_color = PropertyColor::wrap(node_ref, Role::Internal, "bg_color").unwrap();
let sep_size = PropertyFloat32::wrap(node_ref, Role::Internal, "sep_size", 0).unwrap();
let sep_color = PropertyColor::wrap(node_ref, Role::Internal, "sep_color").unwrap();
let active_color = PropertyColor::wrap(node_ref, Role::Internal, "active_color").unwrap();
let alert_color = PropertyColor::wrap(node_ref, Role::Internal, "alert_color").unwrap();
let scroll_start_accel =
PropertyFloat32::wrap(node_ref, Role::Internal, "scroll_start_accel", 0).unwrap();
@@ -166,6 +178,8 @@ impl Menu {
bg_color,
sep_size,
sep_color,
active_color,
alert_color,
window_scale,
mouse_pos: SyncMutex::new(Point::new(0., 0.)),
touch_info: SyncMutex::new(None),
@@ -174,6 +188,7 @@ impl Menu {
motion_cv,
speed: AtomicF32::new(0.),
parent_rect: SyncMutex::new(None),
item_states: SyncMutex::new(HashMap::new()),
});
Pimpl::Menu(self_)
@@ -205,6 +220,9 @@ impl Menu {
async fn handle_selection(&self, item_idx: usize) {
if item_idx < self.items.get_len() {
let item_name = self.items.get_str(item_idx).unwrap();
self.item_states.lock().remove(&item_name);
let node = self.node.upgrade().unwrap();
let data = serialize(&item_name);
node.trigger("select", data).await.unwrap();
@@ -227,6 +245,8 @@ impl Menu {
let padding_x = self.padding.get_f32(0).unwrap();
let padding_y = self.padding.get_f32(1).unwrap();
let text_color = self.text_color.get();
let active_color = self.active_color.get();
let alert_color = self.alert_color.get();
let bg_color = self.bg_color.get();
let sep_size = self.sep_size.get();
let sep_color = self.sep_color.get();
@@ -248,13 +268,21 @@ impl Menu {
sep_mesh.draw_filled_box(&Rectangle::new(0., 0., rect.w, sep_size), sep_color);
let sep_mesh = sep_mesh.alloc(&self.renderer).draw_untextured();
let item_states = self.item_states.lock();
for idx in 0..num_items {
let item_text = self.items.get_str(idx).unwrap();
let color = match item_states.get(&item_text) {
Some(ItemStatus::Active) => active_color,
Some(ItemStatus::Alert) => alert_color,
_ => text_color,
};
// Draw text
let layout = text::make_layout(
&item_text,
text_color,
color,
font_size,
1.0,
window_scale,
@@ -302,12 +330,9 @@ impl Menu {
})
}
async fn redraw(self: Arc<Self>, batch: BatchGuardPtr) {
fn redraw(&self, atom: &mut PropertyAtomicGuard) {
let Some(parent_rect) = self.parent_rect.lock().clone() else { return };
let atom = &mut batch.spawn();
let Some(draw_update) = self.get_draw_calls(atom, parent_rect) else { return };
self.renderer.replace_draw_calls(Some(atom.batch_id), draw_update.draw_calls);
}
@@ -385,6 +410,72 @@ impl Menu {
}
}
}
async fn process_mark_active_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
let Ok(method_call) = sub.receive().await else {
d!("Event relayer closed");
return false
};
d!("method called: mark_active({method_call:?})");
assert!(method_call.send_res.is_none());
fn decode_data(data: &[u8]) -> std::io::Result<String> {
use std::io::Cursor;
let mut cur = Cursor::new(&data);
let item_name = String::decode(&mut cur)?;
Ok(item_name)
}
let Ok(item_name) = decode_data(&method_call.data) else {
d!("mark_active() method invalid arg data");
return true
};
let Some(self_) = me.upgrade() else {
d!("Self destroyed");
return true
};
self_.item_states.lock().insert(item_name, ItemStatus::Active);
let atom = &mut self_.renderer.make_guard(gfxtag!("Menu::mark_active"));
self_.redraw(atom);
true
}
async fn process_mark_alert_method(me: &Weak<Self>, sub: &MethodCallSub) -> bool {
let Ok(method_call) = sub.receive().await else {
d!("Event relayer closed");
return false
};
d!("method called: mark_alert({method_call:?})");
assert!(method_call.send_res.is_none());
fn decode_data(data: &[u8]) -> std::io::Result<String> {
use std::io::Cursor;
let mut cur = Cursor::new(&data);
let item_name = String::decode(&mut cur)?;
Ok(item_name)
}
let Ok(item_name) = decode_data(&method_call.data) else {
d!("mark_alert() method invalid arg data");
return true
};
let Some(self_) = me.upgrade() else {
d!("Self destroyed");
return true
};
self_.item_states.lock().insert(item_name, ItemStatus::Alert);
let atom = &mut self_.renderer.make_guard(gfxtag!("Menu::mark_alert"));
self_.redraw(atom);
true
}
}
#[async_trait]
@@ -395,6 +486,7 @@ impl UIObject for Menu {
async fn start(self: Arc<Self>, ex: ExecutorPtr) {
let me = Arc::downgrade(&self);
let node_ref = &self.node.upgrade().unwrap();
let me2 = me.clone();
let cv = self.motion_cv.clone();
@@ -409,18 +501,37 @@ impl UIObject for Menu {
}
});
let method_sub = node_ref.subscribe_method_call("mark_active").unwrap();
let me2 = me.clone();
let mark_active_task =
ex.spawn(
async move { while Self::process_mark_active_method(&me2, &method_sub).await {} },
);
let method_sub = node_ref.subscribe_method_call("mark_alert").unwrap();
let me2 = me.clone();
let mark_alert_task =
ex.spawn(
async move { while Self::process_mark_alert_method(&me2, &method_sub).await {} },
);
let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
on_modify.when_change(self.items.clone(), Self::redraw);
on_modify.when_change(self.rect.prop(), Self::redraw);
on_modify.when_change(self.font_size.prop(), Self::redraw);
on_modify.when_change(self.padding.clone(), Self::redraw);
on_modify.when_change(self.text_color.prop(), Self::redraw);
on_modify.when_change(self.bg_color.prop(), Self::redraw);
on_modify.when_change(self.sep_size.prop(), Self::redraw);
on_modify.when_change(self.sep_color.prop(), Self::redraw);
async fn redraw(self_: Arc<Menu>, batch: BatchGuardPtr) {
let atom = &mut batch.spawn();
self_.redraw(atom);
}
let mut tasks = vec![motion_task];
on_modify.when_change(self.items.clone(), redraw);
on_modify.when_change(self.rect.prop(), redraw);
on_modify.when_change(self.font_size.prop(), redraw);
on_modify.when_change(self.padding.clone(), redraw);
on_modify.when_change(self.text_color.prop(), redraw);
on_modify.when_change(self.bg_color.prop(), redraw);
on_modify.when_change(self.sep_size.prop(), redraw);
on_modify.when_change(self.sep_color.prop(), redraw);
let mut tasks = vec![motion_task, mark_active_task, mark_alert_task];
tasks.append(&mut on_modify.tasks);
*self.tasks.lock() = tasks;
}