// panel_botonera.rs — Botonera remota use gtk4::prelude::*; use gtk4::{ Align, Box as GtkBox, Button, Entry, FlowBox, Label, Orientation, ScrolledWindow, Separator, }; use std::cell::RefCell; use std::rc::Rc; use crate::conexion::Conexion; use crate::protocol::{BotonRemoto, MsgCliente}; #[derive(Clone)] pub struct PanelBotonera { pub root: GtkBox, flow: FlowBox, conexion: Rc>>, botones: Rc>>, } impl PanelBotonera { pub fn nuevo(conexion: &Rc>>) -> Self { let root = GtkBox::new(Orientation::Vertical, 4); root.set_margin_top(6); root.set_margin_bottom(6); root.set_margin_start(6); root.set_margin_end(6); let toolbar = GtkBox::new(Orientation::Horizontal, 6); let btn_cargar = Button::with_label("⟳ Cargar"); let btn_guardar = Button::with_label("💾 Guardar"); btn_guardar.add_css_class("suggested-action"); toolbar.append(&btn_cargar); toolbar.append(&btn_guardar); root.append(&toolbar); root.append(&Separator::new(Orientation::Horizontal)); let lbl = Label::new(Some( "Clic → reproducir en servidor. Clic derecho → editar botón.", )); lbl.add_css_class("dim-label"); lbl.set_margin_top(8); lbl.set_margin_bottom(8); root.append(&lbl); let flow = FlowBox::new(); flow.set_max_children_per_line(8); flow.set_min_children_per_line(4); flow.set_selection_mode(gtk4::SelectionMode::None); flow.set_homogeneous(true); flow.set_row_spacing(4); flow.set_column_spacing(4); flow.set_margin_top(8); flow.set_margin_bottom(8); flow.set_margin_start(8); flow.set_margin_end(8); let scroll = ScrolledWindow::new(); scroll.set_vexpand(true); scroll.set_child(Some(&flow)); root.append(&scroll); let botones: Rc>> = Rc::new(RefCell::new(Vec::new())); { let conexion2 = conexion.clone(); btn_cargar.connect_clicked(move |_| { if let Some(ref con) = *conexion2.borrow() { con.enviar(MsgCliente::ObtenerBotonera); } }); } { let botones2 = botones.clone(); let conexion2 = conexion.clone(); btn_guardar.connect_clicked(move |_| { let lista = botones2.borrow().clone(); if let Some(ref con) = *conexion2.borrow() { con.enviar(MsgCliente::GuardarBotonera { botones: lista }); } }); } PanelBotonera { root, flow, conexion: conexion.clone(), botones } } pub fn cargar_botones(&self, botones: &[BotonRemoto]) { while let Some(child) = self.flow.first_child() { self.flow.remove(&child); } *self.botones.borrow_mut() = botones.to_vec(); for boton in botones { let btn = Button::with_label(&boton.etiqueta); btn.set_size_request(90, 60); btn.add_css_class("botonera-btn"); let ruta = boton.ruta.clone(); let conexion2 = self.conexion.clone(); btn.connect_clicked(move |_| { if let Some(ref con) = *conexion2.borrow() { con.enviar(MsgCliente::ReproducirAhora { ruta: ruta.clone() }); } }); // Clic derecho → editar let gc = gtk4::GestureClick::new(); gc.set_button(3); let btn2 = btn.clone(); let indice = boton.indice; let botones2 = self.botones.clone(); gc.connect_pressed(move |_gc, _n, _x, _y| { let padre: Option = btn2 .root() .and_then(|r| r.downcast::().ok()); abrir_dialogo_editar_boton( padre.as_ref(), &btn2.label().unwrap_or_default(), { let b = botones2.borrow(); b.iter().find(|b| b.indice == indice) .map(|b| b.ruta.clone()) .unwrap_or_default() }, { let btn3 = btn2.clone(); let b2 = botones2.clone(); move |nueva_lbl, nueva_ruta| { btn3.set_label(&nueva_lbl); if let Some(b) = b2.borrow_mut().iter_mut().find(|b| b.indice == indice) { b.etiqueta = nueva_lbl; b.ruta = nueva_ruta; } } }, ); }); btn.add_controller(gc); self.flow.insert(&btn, -1); } } pub fn set_activo(&self, activo: bool) { self.flow.set_sensitive(activo); } } fn abrir_dialogo_editar_boton( padre: Option<>k4::Window>, etiqueta_actual: &str, ruta_actual: String, on_accept: F, ) { let win = gtk4::Window::new(); win.set_title(Some("Editar botón")); win.set_modal(true); win.set_resizable(false); if let Some(p) = padre { win.set_transient_for(Some(p)); } let vbox = GtkBox::new(Orientation::Vertical, 8); vbox.set_margin_top(12); vbox.set_margin_bottom(12); vbox.set_margin_start(16); vbox.set_margin_end(16); let f1 = GtkBox::new(Orientation::Horizontal, 8); f1.append(&Label::new(Some("Etiqueta:"))); let e_lbl = Entry::new(); e_lbl.set_text(etiqueta_actual); e_lbl.set_hexpand(true); f1.append(&e_lbl); vbox.append(&f1); let f2 = GtkBox::new(Orientation::Horizontal, 8); f2.append(&Label::new(Some("Ruta:"))); let e_ruta = Entry::new(); e_ruta.set_text(&ruta_actual); e_ruta.set_hexpand(true); f2.append(&e_ruta); vbox.append(&f2); let fila_btn = GtkBox::new(Orientation::Horizontal, 8); fila_btn.set_halign(Align::End); fila_btn.set_margin_top(8); let btn_cancel = Button::with_label("Cancelar"); let btn_ok = Button::with_label("Aceptar"); btn_ok.add_css_class("suggested-action"); fila_btn.append(&btn_cancel); fila_btn.append(&btn_ok); vbox.append(&fila_btn); win.set_child(Some(&vbox)); let on_accept = Rc::new(on_accept); { let win2 = win.clone(); let e_lbl2 = e_lbl.clone(); let e_ruta2 = e_ruta.clone(); let oa2 = on_accept.clone(); btn_ok.connect_clicked(move |_| { on_accept(e_lbl2.text().to_string(), e_ruta2.text().to_string()); win2.close(); }); } { let win2 = win.clone(); btn_cancel.connect_clicked(move |_| { win2.close(); }); } win.present(); }