Compare commits

...

8 commits
0.3.5 ... main

Author SHA1 Message Date
Stéphane Cerveau c309e94db2 graphbook: Use a scrolledwindow instead of ViewPort
In order to support the zoom, the graphbook must be
a scrolled window and not a ViewPort
2024-05-27 13:43:19 +02:00
Stéphane Cerveau c60fbf8342 player: use default value 2024-05-27 13:41:47 +02:00
Stéphane Cerveau 671fd8ffd2 graphview: allow to zoom in or zoom out in the graph 2024-05-27 12:26:07 +02:00
Stéphane Cerveau b8a92586d4 gtk: update to 0.8.2
update other packages to latter versions with cargo update
2024-05-21 11:40:07 +02:00
Stéphane Cerveau 1f200f4f30 app: update to gtk 0.8.0 and gst 0.22.2
In the change, the glib::channel has been dropped to use
async_io which achieves the same to receive message
for the logger and display it in the treeview.
Another message is received when a new gtkpaintablesink
has been instanciated.
2024-03-14 16:34:55 +01:00
Stéphane Cerveau 44d64ccdc4 cargo: update gtk and gst crate
gtk4 0.7.3
gst 0.21.3
gst-plugins-gtk 0.11.3
2024-01-25 20:04:38 +01:00
Stéphane Cerveau 3985458832 properties: can now support ParamSpecFloat 2024-01-23 10:29:57 +01:00
Stéphane Cerveau 7a70feedba pages: update the pages with release 0.3.5 2024-01-05 16:34:13 +01:00
10 changed files with 983 additions and 552 deletions

871
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,9 @@ rust-version = "1.70.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
gtk = { version = "0.7.2", package = "gtk4" } gtk = { version = "0.8.2", package = "gtk4" }
gst = { package = "gstreamer", version = "0.21.0" } gst = { package = "gstreamer", version = "0.22.2" }
gst-plugin-gtk4 = { version = "0.11.0", optional=true } gst-plugin-gtk4 = { version = "0.12.1", optional=true }
anyhow = "1" anyhow = "1"
log = "0.4.11" log = "0.4.11"
once_cell = "1.7.2" once_cell = "1.7.2"
@ -21,6 +21,8 @@ futures-channel = "0.3"
lazy_static = "1.4" lazy_static = "1.4"
chrono = "0.4" chrono = "0.4"
structopt = "0.3" structopt = "0.3"
async-channel = "2.0.0"
[dev-dependencies] [dev-dependencies]
futures-executor = "0.3" futures-executor = "0.3"

View file

@ -22,12 +22,12 @@
<ul> <ul>
<li><a href="https://flathub.org/apps/org.freedesktop.dabrain34.GstPipelineStudio">Flathub</a></li> <li><a href="https://flathub.org/apps/org.freedesktop.dabrain34.GstPipelineStudio">Flathub</a></li>
<li><a <li><a
href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/uploads/ce4b8443e2d3161eeaed089071cdc402/GstPipelineStudio-0.3.4.msi">Windows href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/uploads/0ab4098a9385f21d72881d27530e6e27/GstPipelineStudio-0.3.5.msi">Windows
MSI</a> MSI</a>
</li> </li>
<li><a <li><a
href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/uploads/5ac641779cfb7e8fffdf9be6a61fba17/GstPipelineStudio-0.3.4.dmg">MacOS</a> href="https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio/uploads/d6d703809e3023de04e7ad1449fcb4aa/GstPipelineStudio-0.3.5.dmg">MacOS</a>
</li> </li>
</ul> </ul>

View file

@ -446,10 +446,10 @@ impl GPSApp {
pub fn build_ui(&self, application: &Application, pipeline_desc: &String) { pub fn build_ui(&self, application: &Application, pipeline_desc: &String) {
graphbook::setup_graphbook(self); graphbook::setup_graphbook(self);
graphbook::create_graphtab(self, 0, None); graphbook::create_graphtab(self, 0, None);
let (ready_tx, ready_rx) = async_channel::unbounded::<(logger::LogType, String)>();
// Setup the logger to get messages into the TreeView // Setup the logger to get messages into the TreeView
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::Priority::DEFAULT);
let app_weak = self.downgrade();
logger::init_logger( logger::init_logger(
ready_tx.clone(), ready_tx.clone(),
Settings::log_file_path() Settings::log_file_path()
@ -460,9 +460,12 @@ impl GPSApp {
GPSUI::logger::setup_logger_list(self, "treeview-app-logger", logger::LogType::App); GPSUI::logger::setup_logger_list(self, "treeview-app-logger", logger::LogType::App);
GPSUI::logger::setup_logger_list(self, "treeview-msg-logger", logger::LogType::Message); GPSUI::logger::setup_logger_list(self, "treeview-msg-logger", logger::LogType::Message);
GPSUI::logger::setup_logger_list(self, "treeview-gst-logger", logger::LogType::Gst); GPSUI::logger::setup_logger_list(self, "treeview-gst-logger", logger::LogType::Gst);
let _ = ready_rx.attach(None, move |msg: (logger::LogType, String)| { let app_weak = self.downgrade();
let app = upgrade_weak!(app_weak, glib::ControlFlow::Break); glib::spawn_future_local(async move {
GPSUI::logger::add_to_logger_list(&app, msg.0, &msg.1); while let Ok(msg) = ready_rx.recv().await {
let app = upgrade_weak!(app_weak, glib::ControlFlow::Break);
GPSUI::logger::add_to_logger_list(&app, msg.0, &msg.1);
}
glib::ControlFlow::Continue glib::ControlFlow::Continue
}); });

View file

@ -26,10 +26,11 @@ use std::fmt::Write as _;
use std::ops; use std::ops;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PipelineState { pub enum PipelineState {
Playing, Playing,
Paused, Paused,
#[default]
Stopped, Stopped,
Error, Error,
} }
@ -40,7 +41,7 @@ impl fmt::Display for PipelineState {
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct Player(Rc<PlayerInner>); pub struct Player(Rc<PlayerInner>);
// Deref into the contained struct to make usage a bit more ergonomic // Deref into the contained struct to make usage a bit more ergonomic
@ -83,7 +84,7 @@ fn gst_log_handler(
GPS_GST_LOG!("{}", log_message); GPS_GST_LOG!("{}", log_message);
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct PlayerInner { pub struct PlayerInner {
app: RefCell<Option<GPSApp>>, app: RefCell<Option<GPSApp>>,
pipeline: RefCell<Option<gst::Pipeline>>, pipeline: RefCell<Option<gst::Pipeline>>,
@ -101,7 +102,7 @@ impl Player {
n_video_sink: Cell::new(0), n_video_sink: Cell::new(0),
bus_watch_guard: RefCell::new(None), bus_watch_guard: RefCell::new(None),
})); }));
gst::debug_add_log_function(gst_log_handler); gst::log::add_log_function(gst_log_handler);
Ok(pipeline) Ok(pipeline)
} }
@ -123,13 +124,13 @@ impl Player {
.parse::<bool>() .parse::<bool>()
.expect("Should a boolean value") .expect("Should a boolean value")
{ {
ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::Primary); ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::PRIMARY);
} else { } else {
ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::Marginal); ElementInfo::element_update_rank("gtk4paintablesink", gst::Rank::MARGINAL);
} }
gst::debug_set_threshold_from_string(settings::Settings::gst_log_level().as_str(), true); gst::log::set_threshold_from_string(settings::Settings::gst_log_level().as_str(), true);
// Create pipeline from the description // Create pipeline from the description
let pipeline = gst::parse_launch(description)?; let pipeline = gst::parse::launch(description)?;
let pipeline = pipeline.downcast::<gst::Pipeline>(); let pipeline = pipeline.downcast::<gst::Pipeline>();
/* start playing */ /* start playing */
if pipeline.is_err() { if pipeline.is_err() {
@ -141,19 +142,21 @@ impl Player {
self.check_for_gtk4sink(pipeline.as_ref().unwrap()); self.check_for_gtk4sink(pipeline.as_ref().unwrap());
// GPSApp is not Send(trait) ready , so we use a channel to exchange the given data with the main thread and use // GPSApp is not Send(trait) ready , so we use a channel to exchange the given data with the main thread and use
// GPSApp. // GPSApp.
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::Priority::DEFAULT); let (ready_tx, ready_rx) = async_channel::unbounded::<gst::Element>();
let player_weak = self.downgrade(); let player_weak = self.downgrade();
let _ = ready_rx.attach(None, move |element: gst::Element| { glib::spawn_future_local(async move {
let player = upgrade_weak!(player_weak, glib::ControlFlow::Break); while let Ok(element) = ready_rx.recv().await {
let paintable = element.property::<gdk::Paintable>("paintable"); let player = upgrade_weak!(player_weak, glib::ControlFlow::Break);
let n_sink = player.n_video_sink.get(); let paintable = element.property::<gdk::Paintable>("paintable");
player let n_sink = player.n_video_sink.get();
.app player
.borrow() .app
.as_ref() .borrow()
.expect("App should be available") .as_ref()
.set_app_preview(&paintable, n_sink); .expect("App should be available")
player.n_video_sink.set(n_sink + 1); .set_app_preview(&paintable, n_sink);
player.n_video_sink.set(n_sink + 1);
}
glib::ControlFlow::Continue glib::ControlFlow::Continue
}); });
let bin = pipeline.unwrap().dynamic_cast::<gst::Bin>(); let bin = pipeline.unwrap().dynamic_cast::<gst::Bin>();
@ -162,7 +165,7 @@ impl Player {
if let Some(factory) = element.factory() { if let Some(factory) = element.factory() {
GPS_INFO!("Received the signal deep element added {}", factory.name()); GPS_INFO!("Received the signal deep element added {}", factory.name());
if factory.name() == "gtk4paintablesink" { if factory.name() == "gtk4paintablesink" {
let _ = ready_tx.send(element.clone()); let _ = ready_tx.try_send(element.clone());
} }
} }
}); });

View file

@ -19,13 +19,14 @@ use gtk::{gio, glib, graphene};
use std::cell::{Cell, Ref, RefCell}; use std::cell::{Cell, Ref, RefCell};
use std::path::Path; use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq, Default)]
enum TabState { enum TabState {
#[default]
Undefined = 0, Undefined = 0,
Modified, Modified,
Saved, Saved,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct GraphTab { pub struct GraphTab {
graphview: RefCell<GM::GraphView>, graphview: RefCell<GM::GraphView>,
player: RefCell<GPS::Player>, player: RefCell<GPS::Player>,
@ -173,7 +174,7 @@ pub fn setup_graphbook(app: &GPSApp) {
graphbook.connect_switch_page(move |_book, widget, page| { graphbook.connect_switch_page(move |_book, widget, page| {
let graphview = widget let graphview = widget
.first_child() .first_child()
.expect("Unable to get the child from the graphbook, ie the graphview"); .expect("Unable to get the child from the graphbook, ie the scrolledWindow");
if let Ok(graphview) = graphview.dynamic_cast::<GM::GraphView>() { if let Ok(graphview) = graphview.dynamic_cast::<GM::GraphView>() {
let app = upgrade_weak!(app_weak); let app = upgrade_weak!(app_weak);
GPS_TRACE!("graphview.id() {} graphbook page {}", graphview.id(), page); GPS_TRACE!("graphview.id() {} graphbook page {}", graphview.id(), page);
@ -191,8 +192,11 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
.builder .builder
.object("graphbook") .object("graphbook")
.expect("Couldn't get graphbook"); .expect("Couldn't get graphbook");
let drawing_area_window: gtk::Viewport = gtk::Viewport::builder().build();
drawing_area_window.set_child(Some(&*graphtab(app, id).graphview())); let scrollwindow = gtk::ScrolledWindow::builder()
.name("graphview_scroll")
.child(&*graphtab(app, id).graphview())
.build();
let tab_box = gtk::Box::new(gtk::Orientation::Horizontal, 0); let tab_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
let label = gt.widget_label(); let label = gt.widget_label();
@ -209,8 +213,8 @@ pub fn create_graphtab(app: &GPSApp, id: u32, name: Option<&str>) {
graphbook.remove_page(Some(current_graphtab(&app).id())); graphbook.remove_page(Some(current_graphtab(&app).id()));
})); }));
tab_box.append(&close_button); tab_box.append(&close_button);
graphbook.append_page(&drawing_area_window, Some(&tab_box)); graphbook.append_page(&scrollwindow, Some(&tab_box));
graphbook.set_tab_reorderable(&drawing_area_window, true); graphbook.set_tab_reorderable(&scrollwindow, true);
let app_weak = app.downgrade(); let app_weak = app.downgrade();
gt.graphview().connect_local( gt.graphview().connect_local(
"graph-updated", "graph-updated",

View file

@ -25,7 +25,7 @@ use once_cell::sync::Lazy;
use std::io::Cursor; use std::io::Cursor;
use gtk::{ use gtk::{
gdk::{BUTTON_PRIMARY, BUTTON_SECONDARY}, gdk,
glib::{self, clone, subclass::Signal}, glib::{self, clone, subclass::Signal},
graphene, gsk, graphene, gsk,
prelude::*, prelude::*,
@ -39,26 +39,38 @@ use std::{cmp::Ordering, collections::HashMap};
static GRAPHVIEW_STYLE: &str = include_str!("graphview.css"); static GRAPHVIEW_STYLE: &str = include_str!("graphview.css");
pub static GRAPHVIEW_XML_VERSION: &str = "0.1"; pub static GRAPHVIEW_XML_VERSION: &str = "0.1";
const CANVAS_SIZE: f64 = 5000.0;
mod imp { mod imp {
use super::*; use super::*;
use std::{ use std::cell::{Cell, RefCell};
cell::{Cell, RefCell},
rc::Rc,
};
use log::warn; use log::warn;
pub struct DragState {
node: glib::WeakRef<Node>,
/// This stores the offset of the pointer to the origin of the node,
/// so that we can keep the pointer over the same position when moving the node
///
/// The offset is normalized to the default zoom-level of 1.0.
offset: graphene::Point,
}
#[derive(Default)] #[derive(Default)]
pub struct GraphView { pub struct GraphView {
pub(super) id: Cell<u32>, pub(super) id: Cell<u32>,
pub(super) nodes: RefCell<HashMap<u32, Node>>, pub(super) nodes: RefCell<HashMap<u32, (Node, graphene::Point)>>,
pub(super) links: RefCell<HashMap<u32, Link>>, pub(super) links: RefCell<HashMap<u32, Link>>,
pub(super) current_node_id: Cell<u32>, pub(super) current_node_id: Cell<u32>,
pub(super) current_port_id: Cell<u32>, pub(super) current_port_id: Cell<u32>,
pub(super) current_link_id: Cell<u32>, pub(super) current_link_id: Cell<u32>,
pub(super) port_selected: RefCell<Option<Port>>, pub(super) port_selected: RefCell<Option<Port>>,
pub(super) mouse_position: Cell<(f64, f64)>, pub(super) mouse_position: Cell<(f64, f64)>,
pub dragged_node: RefCell<Option<DragState>>,
pub hadjustment: RefCell<Option<gtk::Adjustment>>,
pub vadjustment: RefCell<Option<gtk::Adjustment>>,
pub zoom_factor: Cell<f64>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -66,10 +78,11 @@ mod imp {
const NAME: &'static str = "GraphView"; const NAME: &'static str = "GraphView";
type Type = super::GraphView; type Type = super::GraphView;
type ParentType = gtk::Widget; type ParentType = gtk::Widget;
type Interfaces = (gtk::Scrollable,);
fn class_init(klass: &mut Self::Class) { fn class_init(klass: &mut Self::Class) {
// The layout manager determines how child widgets are laid out. // The layout manager determines how child widgets are laid out.
klass.set_layout_manager_type::<gtk::FixedLayout>(); //klass.set_layout_manager_type::<gtk::FixedLayout>();
klass.set_css_name("graphview"); klass.set_css_name("graphview");
} }
} }
@ -79,65 +92,83 @@ mod imp {
let obj = self.obj(); let obj = self.obj();
self.parent_constructed(); self.parent_constructed();
let drag_state = Rc::new(RefCell::new(None)); self.obj().set_overflow(gtk::Overflow::Hidden);
let drag_controller = gtk::GestureDrag::new(); let drag_controller = gtk::GestureDrag::new();
drag_controller.connect_drag_begin( drag_controller.connect_drag_begin(|drag_controller, x, y| {
clone!(@strong drag_state => move |drag_controller, x, y| { let widget = drag_controller
let mut drag_state = drag_state.borrow_mut(); .widget()
let widget = drag_controller .dynamic_cast::<super::GraphView>()
.widget() .expect("drag-begin event is not on the GraphView");
.dynamic_cast::<Self::Type>() let mut dragged_node = widget.imp().dragged_node.borrow_mut();
.expect("drag-begin event is not on the GraphView");
// pick() should at least return the widget itself. // pick() should at least return the widget itself.
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("drag-begin pick() did not return a widget"); let target = widget
*drag_state = if target.ancestor(Port::static_type()).is_some() { .pick(x, y, gtk::PickFlags::DEFAULT)
// The user targeted a port, so the dragging should be handled by the Port .expect("drag-begin pick() did not return a widget");
// component instead of here. *dragged_node = if target.ancestor(Port::static_type()).is_some() {
None // The user targeted a port, so the dragging should be handled by the Port
} else if let Some(target) = target.ancestor(Node::static_type()) { // component instead of here.
// The user targeted a Node without targeting a specific Port. None
// Drag the Node around the screen. } else if let Some(target) = target.ancestor(Node::static_type()) {
if let Some((x, y)) = widget.node_position(&target) { // The user targeted a Node without targeting a specific Port.
Some((target, x, y)) // Drag the Node around the screen.
} else { let node = target.dynamic_cast_ref::<Node>().unwrap();
error!("Failed to obtain position of dragged node, drag aborted.");
None let Some(canvas_node_pos) = widget.node_position(node) else {
} return;
} else { };
None let canvas_cursor_pos = widget
} .imp()
.screen_space_to_canvas_space_transform()
.transform_point(&graphene::Point::new(x as f32, y as f32));
Some(DragState {
node: node.clone().downgrade(),
offset: graphene::Point::new(
canvas_cursor_pos.x() - canvas_node_pos.x(),
canvas_cursor_pos.y() - canvas_node_pos.y(),
),
})
} else {
None
} }
)); });
drag_controller.connect_drag_update( drag_controller.connect_drag_update(|drag_controller, x, y| {
clone!(@strong drag_state => move |drag_controller, x, y| { let widget = drag_controller
let widget = drag_controller .widget()
.widget() .dynamic_cast::<super::GraphView>()
.dynamic_cast::<Self::Type>() .expect("drag-update event is not on the GraphView");
.expect("drag-update event is not on the GraphView"); let dragged_node = widget.imp().dragged_node.borrow();
let drag_state = drag_state.borrow(); let Some(DragState { node, offset }) = dragged_node.as_ref() else {
if let Some((ref node, x1, y1)) = *drag_state { return;
widget.move_node(node, x1 + x as f32, y1 + y as f32); };
} let Some(node) = node.upgrade() else { return };
}
), let (start_x, start_y) = drag_controller
); .start_point()
drag_controller.connect_drag_end( .expect("Drag has no start point");
clone!(@strong drag_state => move |drag_controller, _x, _y| {
let widget = drag_controller let onscreen_node_origin =
.widget() graphene::Point::new((start_x + x) as f32, (start_y + y) as f32);
.dynamic_cast::<Self::Type>() let transform = widget.imp().screen_space_to_canvas_space_transform();
.expect("drag-end event is not on the GraphView"); let canvas_node_origin = transform.transform_point(&onscreen_node_origin);
widget.graph_updated();
} widget.move_node(
), &node,
); &graphene::Point::new(
canvas_node_origin.x() - offset.x(),
canvas_node_origin.y() - offset.y(),
),
);
});
let gesture = gtk::GestureClick::new(); let gesture = gtk::GestureClick::new();
gesture.set_button(0); gesture.set_button(0);
gesture.connect_pressed( gesture.connect_pressed(
clone!(@weak obj, @weak drag_controller => move |gesture, _n_press, x, y| { clone!(@weak obj, @weak drag_controller => move |gesture, _n_press, x, y| {
if gesture.current_button() == BUTTON_SECONDARY { if gesture.current_button() == gdk::BUTTON_SECONDARY {
let widget = drag_controller.widget() let widget = drag_controller.widget()
.dynamic_cast::<Self::Type>() .dynamic_cast::<Self::Type>()
.expect("click event is not on the GraphView"); .expect("click event is not on the GraphView");
@ -155,7 +186,7 @@ mod imp {
widget.unselect_all(); widget.unselect_all();
obj.emit_by_name::<()>("graph-right-clicked", &[&graphene::Point::new(x as f32,y as f32)]); obj.emit_by_name::<()>("graph-right-clicked", &[&graphene::Point::new(x as f32,y as f32)]);
} }
} else if gesture.current_button() == BUTTON_PRIMARY { } else if gesture.current_button() == gdk::BUTTON_PRIMARY {
let widget = drag_controller.widget() let widget = drag_controller.widget()
.dynamic_cast::<Self::Type>() .dynamic_cast::<Self::Type>()
.expect("click event is not on the GraphView"); .expect("click event is not on the GraphView");
@ -177,7 +208,7 @@ mod imp {
); );
gesture.connect_released(clone!(@weak gesture, @weak obj, @weak drag_controller => move |_gesture, _n_press, x, y| { gesture.connect_released(clone!(@weak gesture, @weak obj, @weak drag_controller => move |_gesture, _n_press, x, y| {
if gesture.current_button() == BUTTON_PRIMARY { if gesture.current_button() == gdk::BUTTON_PRIMARY {
let widget = drag_controller let widget = drag_controller
.widget() .widget()
.dynamic_cast::<Self::Type>() .dynamic_cast::<Self::Type>()
@ -229,6 +260,8 @@ mod imp {
info!("double clicked link id {}", link.id()); info!("double clicked link id {}", link.id());
obj.emit_by_name::<()>("link-double-clicked", &[&link.id(), &graphene::Point::new(x as f32,y as f32)]); obj.emit_by_name::<()>("link-double-clicked", &[&link.id(), &graphene::Point::new(x as f32,y as f32)]);
} }
} else {
info!("double click {}",widget.width());
} }
// Click to something else than a port // Click to something else than a port
@ -237,27 +270,49 @@ mod imp {
} }
} }
})); }));
obj.add_controller(drag_controller);
obj.add_controller(gesture);
let event_motion = gtk::EventControllerMotion::new(); let event_motion = gtk::EventControllerMotion::new();
event_motion.connect_motion(glib::clone!(@weak obj => move |_e, x, y| { event_motion.connect_motion(glib::clone!(@weak obj => move |_e, x, y| {
let graphview = obj; let graphview = obj;
if graphview.selected_port().is_some() { if graphview.selected_port().is_some() {
graphview.set_mouse_position(x,y); graphview.set_mouse_position(x,y);
graphview.queue_draw(); graphview.queue_allocate();
} }
})); }));
obj.add_controller(drag_controller);
obj.add_controller(gesture);
obj.add_controller(event_motion); obj.add_controller(event_motion);
let scroll_controller =
gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES);
scroll_controller.connect_scroll(|eventcontroller, _, delta_y| {
let event = eventcontroller.current_event().unwrap(); // We are inside the event handler, so it must have an event
if event
.modifier_state()
.contains(gdk::ModifierType::CONTROL_MASK)
{
let widget = eventcontroller
.widget()
.downcast::<super::GraphView>()
.unwrap();
widget.set_zoom_factor(widget.zoom_factor() + (0.1 * -delta_y), None);
glib::Propagation::Stop
} else {
glib::Propagation::Proceed
}
});
self.obj().add_controller(scroll_controller);
} }
fn dispose(&self) { fn dispose(&self) {
self.nodes self.nodes
.borrow() .borrow()
.values() .values()
.for_each(|node| node.unparent()) .for_each(|(node, _)| node.unparent())
} }
fn signals() -> &'static [Signal] { fn signals() -> &'static [Signal] {
@ -301,18 +356,97 @@ mod imp {
}); });
SIGNALS.as_ref() SIGNALS.as_ref()
} }
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("hadjustment"),
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("vadjustment"),
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("hscroll-policy"),
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("vscroll-policy"),
glib::ParamSpecDouble::builder("zoom-factor")
.minimum(0.3)
.maximum(4.0)
.default_value(1.0)
.flags(glib::ParamFlags::CONSTRUCT | glib::ParamFlags::READWRITE)
.build(),
]
});
PROPERTIES.as_ref()
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"hadjustment" => self.hadjustment.borrow().to_value(),
"vadjustment" => self.vadjustment.borrow().to_value(),
"hscroll-policy" | "vscroll-policy" => gtk::ScrollablePolicy::Natural.to_value(),
"zoom-factor" => self.zoom_factor.get().to_value(),
_ => unimplemented!(),
}
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
let obj = self.obj();
match pspec.name() {
"hadjustment" => {
obj.set_adjustment(&obj, value.get().ok(), gtk::Orientation::Horizontal)
}
"vadjustment" => {
obj.set_adjustment(&obj, value.get().ok(), gtk::Orientation::Vertical)
}
"hscroll-policy" | "vscroll-policy" => {}
"zoom-factor" => {
self.zoom_factor.set(value.get().unwrap());
obj.queue_allocate();
}
_ => unimplemented!(),
}
}
} }
impl WidgetImpl for GraphView { impl WidgetImpl for GraphView {
fn size_allocate(&self, _width: i32, _height: i32, baseline: i32) {
let widget = &*self.obj();
let zoom_factor = self.zoom_factor.get();
for (node, point) in self.nodes.borrow().values() {
let (_, natural_size) = node.preferred_size();
let transform = self
.canvas_space_to_screen_space_transform()
.translate(point);
node.allocate(
(natural_size.width() as f64 * zoom_factor).ceil() as i32,
(natural_size.height() as f64 * zoom_factor).ceil() as i32,
baseline,
Some(transform),
);
}
if let Some(ref hadjustment) = *self.hadjustment.borrow() {
widget.set_adjustment_values(widget, hadjustment, gtk::Orientation::Horizontal);
}
if let Some(ref vadjustment) = *self.vadjustment.borrow() {
widget.set_adjustment_values(widget, vadjustment, gtk::Orientation::Vertical);
}
}
fn snapshot(&self, snapshot: &gtk::Snapshot) { fn snapshot(&self, snapshot: &gtk::Snapshot) {
/* FIXME: A lot of hardcoded values in here. /* FIXME: A lot of hardcoded values in here.
Try to use relative units (em) and colours from the theme as much as possible. */ Try to use relative units (em) and colours from the theme as much as possible. */
let widget = &*self.obj();
let alloc = widget.allocation();
// Draw all children // Draw all children
// Draw all visible children
self.nodes self.nodes
.borrow() .borrow()
.values() .values()
.for_each(|node| self.obj().snapshot_child(node, snapshot)); // Cull nodes from rendering when they are outside the visible canvas area
.filter(|(node, _)| alloc.intersect(&node.allocation()).is_some())
.for_each(|(node, _)| widget.snapshot_child(node, snapshot));
for link in self.links.borrow().values() { for link in self.links.borrow().values() {
if let Some((from_x, from_y, to_x, to_y)) = self.link_coordinates(link) { if let Some((from_x, from_y, to_x, to_y)) = self.link_coordinates(link) {
@ -353,56 +487,77 @@ mod imp {
} }
} }
impl ScrollableImpl for GraphView {}
impl GraphView { impl GraphView {
/// Returns a [`gsk::Transform`] matrix that can translate from canvas space to screen space.
///
/// Canvas space is non-zoomed, and (0, 0) is fixed at the middle of the graph. \
/// Screen space is zoomed and adjusted for scrolling, (0, 0) is at the top-left corner of the window.
///
/// This is the inverted form of [`Self::screen_space_to_canvas_space_transform()`].
fn canvas_space_to_screen_space_transform(&self) -> gsk::Transform {
let hadj = self.hadjustment.borrow().as_ref().unwrap().value();
let vadj = self.vadjustment.borrow().as_ref().unwrap().value();
let zoom_factor = self.zoom_factor.get();
gsk::Transform::new()
.translate(&graphene::Point::new(-hadj as f32, -vadj as f32))
.scale(zoom_factor as f32, zoom_factor as f32)
}
/// Returns a [`gsk::Transform`] matrix that can translate from screen space to canvas space.
///
/// This is the inverted form of [`Self::canvas_space_to_screen_space_transform()`], see that function for a more detailed explanation.
fn screen_space_to_canvas_space_transform(&self) -> gsk::Transform {
self.canvas_space_to_screen_space_transform()
.invert()
.unwrap()
}
fn link_from_coordinates(&self, node_from: u32, port_from: u32) -> (f64, f64) { fn link_from_coordinates(&self, node_from: u32, port_from: u32) -> (f64, f64) {
let nodes = self.nodes.borrow(); let nodes = self.nodes.borrow();
let widget = &*self.obj();
let from_node = nodes let from_node = nodes
.get(&node_from) .get(&node_from)
.unwrap_or_else(|| (panic!("Unable to get node from {}", node_from))); .unwrap_or_else(|| (panic!("Unable to get node from {}", node_from)));
let from_port = from_node let from_port = from_node
.0
.port(port_from) .port(port_from)
.unwrap_or_else(|| panic!("Unable to get port from {}", port_from)); .unwrap_or_else(|| panic!("Unable to get port from {}", port_from));
let (mut from_x, mut from_y, fw, fh) = (
from_port.allocation().x(),
from_port.allocation().y(),
from_port.allocation().width(),
from_port.allocation().height(),
);
let (fnx, fny) = (from_node.allocation().x(), from_node.allocation().y());
if let Some((port_x, port_y)) = from_port.translate_coordinates(from_node, 0.0, 0.0) { let (x, y) = from_port
from_x = fnx + fw + port_x as i32; .translate_coordinates(
from_y = fny + (fh / 2) + port_y as i32; widget,
} (from_port.width() / 2) as f64,
(from_port.height() / 2) as f64,
)
.unwrap();
(from_x as f64, from_y as f64) (x, y)
} }
fn link_to_coordinates(&self, node_to: u32, port_to: u32) -> (f64, f64) { fn link_to_coordinates(&self, node_to: u32, port_to: u32) -> (f64, f64) {
let nodes = self.nodes.borrow(); let nodes = self.nodes.borrow();
let widget = &*self.obj();
let to_node = nodes let to_node = nodes
.get(&node_to) .get(&node_to)
.unwrap_or_else(|| panic!("Unable to get node to {}", node_to)); .unwrap_or_else(|| panic!("Unable to get node to {}", node_to));
let to_port = to_node let to_port = to_node
.0
.port(port_to) .port(port_to)
.unwrap_or_else(|| panic!("Unable to get port to {}", port_to)); .unwrap_or_else(|| panic!("Unable to get port to {}", port_to));
let (mut to_x, mut to_y, th) = ( let (x, y) = to_port
to_port.allocation().x(), .translate_coordinates(
to_port.allocation().y(), widget,
to_port.allocation().height(), (to_port.width() / 2) as f64,
); (to_port.height() / 2) as f64,
)
.unwrap();
let (tnx, tny) = (to_node.allocation().x(), to_node.allocation().y()); (x, y)
if let Some((port_x, port_y)) = to_port.translate_coordinates(to_node, 0.0, 0.0) {
to_x += tnx + port_x as i32;
to_y = tny + (th / 2) + port_y as i32;
}
//trace!("{} {} -> {} {}", fx, fy, tx, ty);
(to_x.into(), to_y.into())
} }
/// Retrieves coordinates for the drawn link to start at and to end at. /// Retrieves coordinates for the drawn link to start at and to end at.
/// ///
@ -469,6 +624,8 @@ glib::wrapper! {
} }
impl GraphView { impl GraphView {
pub const ZOOM_MIN: f64 = 0.3;
pub const ZOOM_MAX: f64 = 4.0;
/// Create a new graphview /// Create a new graphview
/// ///
/// # Returns /// # Returns
@ -482,6 +639,7 @@ impl GraphView {
&provider, &provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
); );
glib::Object::new::<Self>() glib::Object::new::<Self>()
} }
@ -505,6 +663,40 @@ impl GraphView {
self.remove_all_nodes(); self.remove_all_nodes();
} }
pub fn zoom_factor(&self) -> f64 {
self.property("zoom-factor")
}
pub fn set_zoom_factor(&self, zoom_factor: f64, anchor: Option<(f64, f64)>) {
let private = imp::GraphView::from_obj(self);
let zoom_factor = zoom_factor.clamp(Self::ZOOM_MIN, Self::ZOOM_MAX);
let (anchor_x_screen, anchor_y_screen) = anchor.unwrap_or_else(|| {
(
self.allocation().width() as f64 / 2.0,
self.allocation().height() as f64 / 2.0,
)
});
let old_zoom = private.zoom_factor.get();
let hadjustment_ref = private.hadjustment.borrow();
let vadjustment_ref = private.vadjustment.borrow();
let hadjustment = hadjustment_ref.as_ref().unwrap();
let vadjustment = vadjustment_ref.as_ref().unwrap();
let x_total = (anchor_x_screen + hadjustment.value()) / old_zoom;
let y_total = (anchor_y_screen + vadjustment.value()) / old_zoom;
let new_hadjustment = x_total * zoom_factor - anchor_x_screen;
let new_vadjustment = y_total * zoom_factor - anchor_y_screen;
hadjustment.set_value(new_hadjustment);
vadjustment.set_value(new_vadjustment);
self.set_property("zoom-factor", zoom_factor);
info!("zoom factor {}", zoom_factor);
}
// Node // Node
/// Create a new node with a new id /// Create a new node with a new id
@ -560,9 +752,10 @@ impl GraphView {
.nodes .nodes
.borrow() .borrow()
.values() .values()
.filter_map(|node| { .map(|node| {
// Map nodes to locations, discard nodes without location // Map nodes to their locations
self.node_position(&node.clone().upcast()) let point = self.node_position(&node.0.clone().upcast()).unwrap();
(point.x(), point.y())
}) })
.filter(|(x2, _)| { .filter(|(x2, _)| {
// Only look for other nodes that have a similar x coordinate // Only look for other nodes that have a similar x coordinate
@ -572,11 +765,13 @@ impl GraphView {
// Get max in column // Get max in column
y1.partial_cmp(y2).unwrap_or(Ordering::Equal) y1.partial_cmp(y2).unwrap_or(Ordering::Equal)
}) })
.map_or(20_f32, |(_x, y)| y + 100.0); .map_or(20_f32, |(_x, y)| y + 120.0);
self.move_node(&node.clone().upcast(), x, y);
let node_id = node.id(); let node_id = node.id();
private.nodes.borrow_mut().insert(node.id(), node); private
.nodes
.borrow_mut()
.insert(node.id(), (node, graphene::Point::new(x, y)));
self.emit_by_name::<()>("node-added", &[&private.id.get(), &node_id]); self.emit_by_name::<()>("node-added", &[&private.id.get(), &node_id]);
self.graph_updated(); self.graph_updated();
} }
@ -585,17 +780,16 @@ impl GraphView {
/// ///
pub fn remove_node(&self, id: u32) { pub fn remove_node(&self, id: u32) {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
let mut nodes = private.nodes.borrow_mut();
if let Some(node) = nodes.remove(&id) { if let Some(node) = private.nodes.borrow_mut().remove(&id) {
while let Some(link_id) = self.node_is_linked(node.id()) { while let Some(link_id) = self.node_is_linked(node.0.id()) {
info!("Remove link id {}", link_id); info!("Remove link id {}", link_id);
private.links.borrow_mut().remove(&link_id); private.links.borrow_mut().remove(&link_id);
} }
node.unparent(); node.0.unparent();
} else { } else {
warn!("Tried to remove non-existent node (id={}) from graph", id); warn!("Tried to remove non-existent node (id={}) from graph", id);
} }
self.queue_draw();
} }
/// Select all nodes according to the NodeType /// Select all nodes according to the NodeType
@ -607,9 +801,9 @@ impl GraphView {
let nodes_list: Vec<_> = nodes let nodes_list: Vec<_> = nodes
.iter() .iter()
.filter(|(_, node)| { .filter(|(_, node)| {
*node.node_type().unwrap() == node_type || node_type == NodeType::All *node.0.node_type().unwrap() == node_type || node_type == NodeType::All
}) })
.map(|(_, node)| node.clone()) .map(|(_, node)| node.0.clone())
.collect(); .collect();
nodes_list nodes_list
} }
@ -619,7 +813,12 @@ impl GraphView {
/// Returns `None` if the node is not in the graphview. /// Returns `None` if the node is not in the graphview.
pub fn node(&self, id: u32) -> Option<Node> { pub fn node(&self, id: u32) -> Option<Node> {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
private.nodes.borrow().get(&id).cloned()
if let Some(node) = private.nodes.borrow().get(&id).cloned() {
Some(node.0)
} else {
None
}
} }
/// Get the node with the specified node name inside the graphview. /// Get the node with the specified node name inside the graphview.
@ -628,8 +827,8 @@ impl GraphView {
pub fn node_by_unique_name(&self, unique_name: &str) -> Option<Node> { pub fn node_by_unique_name(&self, unique_name: &str) -> Option<Node> {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
for node in private.nodes.borrow().values() { for node in private.nodes.borrow().values() {
if node.unique_name() == unique_name { if node.0.unique_name() == unique_name {
return Some(node.clone()); return Some(node.0.clone());
} }
} }
None None
@ -646,7 +845,6 @@ impl GraphView {
private.current_node_id.set(0); private.current_node_id.set(0);
private.current_port_id.set(0); private.current_port_id.set(0);
private.current_link_id.set(0); private.current_link_id.set(0);
self.queue_draw();
} }
/// Check if the node is linked /// Check if the node is linked
@ -665,21 +863,12 @@ impl GraphView {
/// Get the position of the specified node inside the graphview. /// Get the position of the specified node inside the graphview.
/// ///
/// Returns `None` if the node is not in the graphview. /// Returns `None` if the node is not in the graphview.
pub(super) fn node_position(&self, node: &gtk::Widget) -> Option<(f32, f32)> { pub(super) fn node_position(&self, node: &Node) -> Option<graphene::Point> {
let layout_manager = self self.imp()
.layout_manager() .nodes
.expect("Failed to get layout manager") .borrow()
.dynamic_cast::<gtk::FixedLayout>() .get(&node.id())
.expect("Failed to cast to FixedLayout"); .map(|(_, point)| *point)
let node = layout_manager
.layout_child(node)
.dynamic_cast::<gtk::FixedLayoutChild>()
.expect("Could not cast to FixedLayoutChild");
let transform = node
.transform()
.expect("Failed to obtain transform from layout child");
Some(transform.to_translate())
} }
// Port // Port
@ -713,9 +902,8 @@ impl GraphView {
/// Return true if the port presence is not always. /// Return true if the port presence is not always.
pub fn can_remove_port(&self, node_id: u32, port_id: u32) -> bool { pub fn can_remove_port(&self, node_id: u32, port_id: u32) -> bool {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
let nodes = private.nodes.borrow(); if let Some(node) = private.nodes.borrow().get(&node_id) {
if let Some(node) = nodes.get(&node_id) { return node.0.can_remove_port(port_id);
return node.can_remove_port(port_id);
} }
warn!("Unable to find a node with the id {}", node_id); warn!("Unable to find a node with the id {}", node_id);
false false
@ -725,12 +913,11 @@ impl GraphView {
/// ///
pub fn remove_port(&self, node_id: u32, port_id: u32) { pub fn remove_port(&self, node_id: u32, port_id: u32) {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
let nodes = private.nodes.borrow(); if let Some(node) = private.nodes.borrow().get(&node_id) {
if let Some(node) = nodes.get(&node_id) {
if let Some(link_id) = self.port_is_linked(port_id) { if let Some(link_id) = self.port_is_linked(port_id) {
self.remove_link(link_id); self.remove_link(link_id);
} }
node.remove_port(port_id); node.0.remove_port(port_id);
} }
} }
@ -864,8 +1051,8 @@ impl GraphView {
} }
} }
for node in private.nodes.borrow_mut().values() { for node in private.nodes.borrow_mut().values() {
if node.selected() { if node.0.selected() {
node_id = Some(node.id()); node_id = Some(node.0.id());
} }
} }
if let Some(id) = link_id { if let Some(id) = link_id {
@ -895,8 +1082,8 @@ impl GraphView {
)?; )?;
//Get the nodes //Get the nodes
let nodes = self.all_nodes(NodeType::All);
for node in nodes { for node in self.all_nodes(NodeType::All) {
writer.write( writer.write(
XMLWEvent::start_element("Node") XMLWEvent::start_element("Node")
.attr("name", &node.name()) .attr("name", &node.name())
@ -1104,12 +1291,13 @@ impl GraphView {
"Node" => { "Node" => {
if let Some(node) = current_node { if let Some(node) = current_node {
let id = node.id(); let id = node.id();
let position = node.position(); let position =
graphene::Point::new(node.position().0, node.position().1);
node.update_properties(&current_node_properties); node.update_properties(&current_node_properties);
current_node_properties.clear(); current_node_properties.clear();
self.add_node(node); self.add_node(node);
if let Some(node) = self.node(id) { if let Some(node) = self.node(id) {
self.move_node(&node.upcast(), position.0, position.1); self.move_node(&node, &position);
} }
self.update_current_node_id(id); self.update_current_node_id(id);
@ -1207,38 +1395,31 @@ impl GraphView {
false false
} }
fn move_node(&self, widget: &gtk::Widget, x: f32, y: f32) { fn move_node(&self, widget: &Node, point: &graphene::Point) {
let node = widget let mut nodes = self.imp().nodes.borrow_mut();
.clone() let node = nodes
.dynamic_cast::<Node>() .get_mut(&widget.id())
.expect("Unable to convert to Node"); .expect("Node is not on the graph");
node.set_position(x, y); node.1 = graphene::Point::new(
let layout_manager = self point.x().clamp(
.layout_manager() -(CANVAS_SIZE / 2.0) as f32,
.expect("Failed to get layout manager") (CANVAS_SIZE / 2.0) as f32 - widget.width() as f32,
.dynamic_cast::<gtk::FixedLayout>() ),
.expect("Failed to cast to FixedLayout"); point.y().clamp(
-(CANVAS_SIZE / 2.0) as f32,
(CANVAS_SIZE / 2.0) as f32 - widget.height() as f32,
),
);
let transform = gsk::Transform::new()
// Nodes should not be able to be dragged out of the view, so we use `max(coordinate, 0.0)` to prevent that.
.translate(&graphene::Point::new(f32::max(x, 0.0), f32::max(y, 0.0)));
layout_manager
.layout_child(widget)
.dynamic_cast::<gtk::FixedLayoutChild>()
.expect("Could not cast to FixedLayoutChild")
.set_transform(&transform);
// FIXME: If links become proper widgets,
// we don't need to redraw the full graph everytime. // we don't need to redraw the full graph everytime.
self.queue_draw(); self.queue_allocate();
} }
fn unselect_nodes(&self) { fn unselect_nodes(&self) {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
for node in private.nodes.borrow_mut().values() { for node in private.nodes.borrow_mut().values() {
node.set_selected(false); node.0.set_selected(false);
node.unselect_all_ports(); node.0.unselect_all_ports();
} }
} }
@ -1286,7 +1467,7 @@ impl GraphView {
fn graph_updated(&self) { fn graph_updated(&self) {
let private = imp::GraphView::from_obj(self); let private = imp::GraphView::from_obj(self);
self.queue_draw(); self.queue_allocate();
self.emit_by_name::<()>("graph-updated", &[&private.id.get()]); self.emit_by_name::<()>("graph-updated", &[&private.id.get()]);
} }
@ -1362,6 +1543,47 @@ impl GraphView {
private.current_port_id.set(port_id); private.current_port_id.set(port_id);
} }
} }
fn set_adjustment(
&self,
obj: &super::GraphView,
adjustment: Option<&gtk::Adjustment>,
orientation: gtk::Orientation,
) {
let private = imp::GraphView::from_obj(self);
match orientation {
gtk::Orientation::Horizontal => *private.hadjustment.borrow_mut() = adjustment.cloned(),
gtk::Orientation::Vertical => *private.vadjustment.borrow_mut() = adjustment.cloned(),
_ => unimplemented!(),
}
if let Some(adjustment) = adjustment {
adjustment.connect_value_changed(clone!(@weak obj => move |_| obj.queue_allocate() ));
}
}
fn set_adjustment_values(
&self,
obj: &super::GraphView,
adjustment: &gtk::Adjustment,
orientation: gtk::Orientation,
) {
let private = imp::GraphView::from_obj(self);
let size = match orientation {
gtk::Orientation::Horizontal => obj.width(),
gtk::Orientation::Vertical => obj.height(),
_ => unimplemented!(),
};
let zoom_factor = private.zoom_factor.get();
adjustment.configure(
adjustment.value(),
-(CANVAS_SIZE / 2.0) * zoom_factor,
(CANVAS_SIZE / 2.0) * zoom_factor,
(f64::from(size) * 0.1) * zoom_factor,
(f64::from(size) * 0.9) * zoom_factor,
f64::from(size) * zoom_factor,
);
}
} }
impl Default for GraphView { impl Default for GraphView {

View file

@ -6,7 +6,6 @@
// //
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use gtk::glib::Sender;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use simplelog::*; use simplelog::*;
use std::fmt; use std::fmt;
@ -116,7 +115,7 @@ macro_rules! GPS_TRACE (
); );
struct WriteAdapter { struct WriteAdapter {
sender: Sender<(LogType, String)>, sender: async_channel::Sender<(LogType, String)>,
buffer: String, buffer: String,
} }
@ -127,10 +126,8 @@ impl io::Write for WriteAdapter {
.push_str(&String::from_utf8(buf.to_vec()).unwrap()); .push_str(&String::from_utf8(buf.to_vec()).unwrap());
if self.buffer.ends_with('\n') { if self.buffer.ends_with('\n') {
self.buffer.pop(); self.buffer.pop();
self.sender let _ = self.sender.try_send((LogType::App, self.buffer.clone()));
.send((LogType::App, self.buffer.clone())) self.buffer.clear();
.unwrap();
self.buffer = String::from("");
} }
Ok(buf.len()) Ok(buf.len())
@ -152,7 +149,7 @@ fn translate_to_simple_logger(log_level: LogLevel) -> LevelFilter {
} }
} }
pub fn init_logger(sender: Sender<(LogType, String)>, log_file: &str) { pub fn init_logger(sender: async_channel::Sender<(LogType, String)>, log_file: &str) {
simplelog::CombinedLogger::init(vec![ simplelog::CombinedLogger::init(vec![
WriteLogger::new( WriteLogger::new(
translate_to_simple_logger(LogLevel::Trace), translate_to_simple_logger(LogLevel::Trace),
@ -204,21 +201,23 @@ pub fn print_log(log_level: LogLevel, msg: String) {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MessageLogger { pub struct MessageLogger {
sender: Sender<(LogType, String)>, sender: async_channel::Sender<(LogType, String)>,
} }
impl MessageLogger { impl MessageLogger {
pub fn new(sender: Sender<(LogType, String)>) -> Self { pub fn new(sender: async_channel::Sender<(LogType, String)>) -> Self {
Self { sender } Self { sender }
} }
pub fn print_log(&self, log_type: LogType, msg: String) { pub fn print_log(&self, log_type: LogType, msg: String) {
let to_send = format!("{}\t{}", Local::now().format("%H:%M:%S"), msg); let to_send = format!("{}\t{}", Local::now().format("%H:%M:%S"), msg);
self.sender.send((log_type, to_send)).unwrap(); self.sender
.try_send((log_type, to_send))
.expect("Unable to send the log");
} }
} }
pub fn init_msg_logger(sender: Sender<(LogType, String)>) { pub fn init_msg_logger(sender: async_channel::Sender<(LogType, String)>) {
let mut msg_logger = MSG_LOGGER.lock().unwrap(); let mut msg_logger = MSG_LOGGER.lock().unwrap();
if msg_logger.is_none() { if msg_logger.is_none() {
// Initialize the variable // Initialize the variable

View file

@ -209,17 +209,11 @@
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="position">400</property> <property name="position">400</property>
<child> <child>
<object class="GtkScrolledWindow" id="drawing_area-window">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="child">
<object class="GtkNotebook" id="graphbook"> <object class="GtkNotebook" id="graphbook">
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
</object> </object>
</property>
</object>
</child> </child>
<child> <child>
<object class="GtkNotebook" id="notebook-debug"> <object class="GtkNotebook" id="notebook-debug">

View file

@ -57,6 +57,7 @@ pub fn property_to_widget<F: Fn(String, String) + 'static>(
glib::ParamSpecInt64::static_type(), glib::ParamSpecInt64::static_type(),
glib::ParamSpecUInt64::static_type(), glib::ParamSpecUInt64::static_type(),
glib::ParamSpecString::static_type(), glib::ParamSpecString::static_type(),
glib::ParamSpecFloat::static_type(),
] ]
.contains(&t) => .contains(&t) =>
{ {