add more signal
This commit is contained in:
parent
293901ff00
commit
e92ed0be34
42
Cargo.lock
generated
42
Cargo.lock
generated
@ -493,6 +493,7 @@ dependencies = [
|
|||||||
"euclid",
|
"euclid",
|
||||||
"femtovg",
|
"femtovg",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
"futures",
|
||||||
"geo",
|
"geo",
|
||||||
"geo-macros",
|
"geo-macros",
|
||||||
"geo-types",
|
"geo-types",
|
||||||
@ -519,6 +520,8 @@ dependencies = [
|
|||||||
"proj5",
|
"proj5",
|
||||||
"quadtree_rs",
|
"quadtree_rs",
|
||||||
"radarg_plugin_interface",
|
"radarg_plugin_interface",
|
||||||
|
"rayon",
|
||||||
|
"regex",
|
||||||
"relm4",
|
"relm4",
|
||||||
"relm4-components",
|
"relm4-components",
|
||||||
"relm4-icons",
|
"relm4-icons",
|
||||||
@ -526,6 +529,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shapefile",
|
"shapefile",
|
||||||
|
"smallvec",
|
||||||
"surfman",
|
"surfman",
|
||||||
"svg",
|
"svg",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -2160,9 +2164,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
@ -3077,9 +3081,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.7.0"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"rayon-core",
|
"rayon-core",
|
||||||
@ -3087,14 +3091,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon-core"
|
name = "rayon-core"
|
||||||
version = "1.11.0"
|
version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
|
||||||
"crossbeam-deque",
|
"crossbeam-deque",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"num_cpus",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3137,9 +3139,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.8.4"
|
version = "1.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -3148,9 +3162,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.2"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "relm4"
|
name = "relm4"
|
||||||
@ -3466,9 +3480,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.10.0"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay-client-toolkit"
|
name = "smithay-client-toolkit"
|
||||||
|
|||||||
@ -78,6 +78,10 @@ serde_json = "1.0.112"
|
|||||||
flate2 = "1.0.28"
|
flate2 = "1.0.28"
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
|
regex = "1.10.3"
|
||||||
|
smallvec = "1.13.1"
|
||||||
|
rayon = "1.8.1"
|
||||||
|
futures = "0.3.30"
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
mod backend;
|
mod backend;
|
||||||
mod imp;
|
mod imp;
|
||||||
use self::backend::CairoBackend;
|
use self::backend::CairoBackend;
|
||||||
use crate::render::{Render, RenderConfig, RenderMotion};
|
use crate::widgets::render::{Render, RenderConfig, RenderMotion};
|
||||||
use glib::{clone, ObjectExt};
|
use glib::{clone, ObjectExt};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{glib, AspectFrame};
|
use gtk::{glib, AspectFrame};
|
||||||
|
|||||||
@ -1,7 +1,17 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
coords::{proj::Mercator, Mapper},
|
||||||
data::{self, CoordType, Radar2d},
|
data::{self, CoordType, Radar2d},
|
||||||
|
pipeline::{
|
||||||
|
offscreen_renderer::OffscreenRenderer, pool::Pool, render_pipeline::RenderResult,
|
||||||
|
utils::Pipeline,
|
||||||
|
},
|
||||||
plugin_system::init_plugin,
|
plugin_system::init_plugin,
|
||||||
render::{predefined::color_mapper::BoundaryNorm, Layer},
|
widgets::{
|
||||||
|
render::{predefined::color_mapper::BoundaryNorm, Layer},
|
||||||
|
CMS,
|
||||||
|
},
|
||||||
PLUGIN_MANAGER,
|
PLUGIN_MANAGER,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,7 +32,7 @@ use ndarray::{Array1, Array2, Array3};
|
|||||||
use radarg_plugin_interface::{Block, DataShape, PluginId, VecResult};
|
use radarg_plugin_interface::{Block, DataShape, PluginId, VecResult};
|
||||||
use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup};
|
use relm4::actions::{AccelsPlus, RelmAction, RelmActionGroup};
|
||||||
use relm4::*;
|
use relm4::*;
|
||||||
use relm4::{gtk, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
|
use relm4::{gtk, Component, ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent};
|
||||||
use relm4_components::open_dialog::{
|
use relm4_components::open_dialog::{
|
||||||
OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings,
|
OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings,
|
||||||
};
|
};
|
||||||
@ -42,12 +52,19 @@ pub enum AppMsg {
|
|||||||
pub struct AppModel {
|
pub struct AppModel {
|
||||||
open_dialog: Controller<OpenDialog>,
|
open_dialog: Controller<OpenDialog>,
|
||||||
control: Controller<ControlPanelModel>,
|
control: Controller<ControlPanelModel>,
|
||||||
|
target_pipeline: HashMap<String, Pipeline>,
|
||||||
render: Controller<MonitorModel>,
|
render: Controller<MonitorModel>,
|
||||||
setting: Controller<SettingModel>,
|
setting: Controller<SettingModel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AppCommand {
|
||||||
|
Prepare,
|
||||||
|
}
|
||||||
|
|
||||||
#[relm4::component(pub)]
|
#[relm4::component(pub)]
|
||||||
impl SimpleComponent for AppModel {
|
impl Component for AppModel {
|
||||||
|
type CommandOutput = AppCommand;
|
||||||
type Init = ();
|
type Init = ();
|
||||||
type Input = AppMsg;
|
type Input = AppMsg;
|
||||||
type Output = ();
|
type Output = ();
|
||||||
@ -143,6 +160,7 @@ impl SimpleComponent for AppModel {
|
|||||||
|
|
||||||
let model = AppModel {
|
let model = AppModel {
|
||||||
open_dialog: dialog,
|
open_dialog: dialog,
|
||||||
|
target_pipeline: HashMap::new(),
|
||||||
control,
|
control,
|
||||||
render,
|
render,
|
||||||
setting,
|
setting,
|
||||||
@ -174,7 +192,6 @@ impl SimpleComponent for AppModel {
|
|||||||
let page_home = view_stack.add_titled(&page_home, Some("renderer"), "Render");
|
let page_home = view_stack.add_titled(&page_home, Some("renderer"), "Render");
|
||||||
page_home.set_icon_name(Some("home-filled"));
|
page_home.set_icon_name(Some("home-filled"));
|
||||||
|
|
||||||
|
|
||||||
let page_setting = model.setting.widget();
|
let page_setting = model.setting.widget();
|
||||||
|
|
||||||
let page_setting = view_stack.add_titled(page_setting, Some("setting"), "Setting");
|
let page_setting = view_stack.add_titled(page_setting, Some("setting"), "Setting");
|
||||||
@ -192,10 +209,39 @@ impl SimpleComponent for AppModel {
|
|||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
|
fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>, root: &Self::Root) {
|
||||||
match msg {
|
match msg {
|
||||||
AppMsg::OpenFile((time, layer)) => {
|
AppMsg::OpenFile((time, layer)) => {
|
||||||
self.render.sender().emit(MonitorInputMsg::AddLayer(layer));
|
if self.target_pipeline.contains_key(&layer.name) {
|
||||||
|
} else {
|
||||||
|
let mut pipeline = Pipeline::new(10);
|
||||||
|
pipeline.add_task(
|
||||||
|
time.timestamp(),
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut offscreen_render = OffscreenRenderer::new(3000, 3000).unwrap();
|
||||||
|
let canvas_wrapper = offscreen_render.create_canvas();
|
||||||
|
let canvas_mutex =
|
||||||
|
std::sync::Arc::new(std::sync::Mutex::new(canvas_wrapper));
|
||||||
|
let f = {
|
||||||
|
let p = layer.get_prepare();
|
||||||
|
let mut _p = p.lock().unwrap();
|
||||||
|
_p.take()
|
||||||
|
};
|
||||||
|
let target = if let Some(f) = f {
|
||||||
|
let imp = layer.get_imp().unwrap();
|
||||||
|
let map: Mapper = Mercator::default().into();
|
||||||
|
let cms = CMS::new(map, (3000.0, 3000.0));
|
||||||
|
let canvas = canvas_mutex.clone();
|
||||||
|
let c = f(imp, canvas, cms).await;
|
||||||
|
Some(c)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
RenderResult::new(canvas_mutex, target.unwrap(), time)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// self.render.sender().emit(MonitorInputMsg::AddLayer(layer));
|
||||||
self.control
|
self.control
|
||||||
.sender()
|
.sender()
|
||||||
.emit(ControlPanelInputMsg::Selection(Some(time)));
|
.emit(ControlPanelInputMsg::Selection(Some(time)));
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
use super::messages::*;
|
use super::messages::*;
|
||||||
use crate::data::{CoordType, Radar2d, RadarData2d};
|
use crate::data::{CoordType, Radar2d, RadarData2d};
|
||||||
use crate::plugin_system::init_plugin;
|
use crate::plugin_system::init_plugin;
|
||||||
use crate::render::predefined::color_mapper::BoundaryNorm;
|
use crate::widgets::render::predefined::color_mapper::BoundaryNorm;
|
||||||
use crate::render::Layer;
|
use crate::widgets::render::Layer;
|
||||||
use crate::timeline::TimeLine;
|
use crate::widgets::timeline::{Selection, TimeLine};
|
||||||
use abi_stable::std_types::RStr;
|
use abi_stable::std_types::RStr;
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use chrono::{prelude::*, DateTime, Duration, TimeZone, Utc};
|
use chrono::{prelude::*, DateTime, Duration, TimeZone, Utc};
|
||||||
@ -145,7 +145,7 @@ impl SimpleComponent for ControlPanelModel {
|
|||||||
#[track = "model.changed(ControlPanelModel::timeline_start())"]
|
#[track = "model.changed(ControlPanelModel::timeline_start())"]
|
||||||
set_time_start: model.timeline_start,
|
set_time_start: model.timeline_start,
|
||||||
#[track = "model.changed(ControlPanelModel::selection())"]
|
#[track = "model.changed(ControlPanelModel::selection())"]
|
||||||
set_selection: model.selection.map(|p| crate::timeline::Selection::Point(p)),
|
set_selection: model.selection.map(|p| Selection::Point(p)),
|
||||||
connect_start_time_notify[sender] => move |time| {
|
connect_start_time_notify[sender] => move |time| {
|
||||||
let time = time.start_time();
|
let time = time.start_time();
|
||||||
sender.input(
|
sender.input(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::render::Layer;
|
use crate::widgets::render::Layer;
|
||||||
|
|
||||||
pub enum MonitorInputMsg {
|
pub enum MonitorInputMsg {
|
||||||
AddLayer(Layer),
|
AddLayer(Layer),
|
||||||
@ -18,7 +18,6 @@ impl Debug for MonitorInputMsg {
|
|||||||
MonitorInputMsg::None => write!(f, "MonitorInputMsg::None"),
|
MonitorInputMsg::None => write!(f, "MonitorInputMsg::None"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use super::messages::{MonitorInputMsg, MonitorOutputMsg};
|
use super::messages::{MonitorInputMsg, MonitorOutputMsg};
|
||||||
use crate::pipeline::offscreen_renderer::OffscreenRenderer;
|
use crate::pipeline::offscreen_renderer::OffscreenRenderer;
|
||||||
use crate::render::{RenderConfig, Target, CMS};
|
use crate::widgets::render::{RenderConfig, Target, CMS};
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::{proj::Mercator, Mapper},
|
coords::{proj::Mercator, Mapper},
|
||||||
dynamic_col::DynamicCol,
|
widgets::dynamic_col::DynamicCol,
|
||||||
render::{Layer, Render},
|
widgets::render::{Layer, Render},
|
||||||
};
|
};
|
||||||
use glib::clone;
|
use glib::clone;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use relm4::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
chart::Chart,
|
chart::Chart,
|
||||||
data::Npz,
|
data::Npz,
|
||||||
render::{predefined::color_mapper::BoundaryNorm, Layer},
|
widgets::render::{predefined::color_mapper::BoundaryNorm, Layer},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::bottom_bar::BottomBarModel;
|
use super::bottom_bar::BottomBarModel;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
data::{self, CoordType, Radar2d},
|
data::{self, CoordType, Radar2d},
|
||||||
render::{predefined::color_mapper::BoundaryNorm, Layer},
|
widgets::render::{predefined::color_mapper::BoundaryNorm, Layer},
|
||||||
PLUGIN_MANAGER,
|
PLUGIN_MANAGER,
|
||||||
};
|
};
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
@ -29,7 +29,9 @@ impl SimpleComponent for SettingModel {
|
|||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_sidebar=&adw::NavigationPage{
|
set_sidebar=&adw::NavigationPage{
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_child=>k::StackSidebar{}
|
set_child=>k::StackSidebar{
|
||||||
|
set_stack:&stack,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
#[wrap(Some)]
|
#[wrap(Some)]
|
||||||
set_content=&adw::NavigationPage{
|
set_content=&adw::NavigationPage{
|
||||||
@ -59,7 +61,19 @@ impl SimpleComponent for SettingModel {
|
|||||||
let general_page = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
let general_page = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
||||||
|
|
||||||
let plugin_page = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
let plugin_page = gtk::Box::new(gtk::Orientation::Vertical, 10);
|
||||||
|
let preferences = adw::PreferencesPage::new();
|
||||||
|
let group = adw::PreferencesGroup::new();
|
||||||
|
group.set_title("Plugins");
|
||||||
|
let entry_row = adw::EntryRow::new();
|
||||||
|
entry_row.set_title("Plugin Path");
|
||||||
|
group.add(&entry_row);
|
||||||
|
preferences.add(&group);
|
||||||
|
preferences.set_parent(&plugin_page);
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
let stack = widgets.stack.clone();
|
||||||
|
|
||||||
|
stack.add_titled(&plugin_page, None, "Plugins");
|
||||||
|
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::render::WindowCoord;
|
use crate::widgets::render::WindowCoord;
|
||||||
use std::borrow::ToOwned;
|
use std::borrow::ToOwned;
|
||||||
|
|
||||||
use super::{proj::ProjectionS, Range};
|
use super::{proj::ProjectionS, Range};
|
||||||
@ -13,7 +13,6 @@ pub struct Mapper {
|
|||||||
|
|
||||||
unsafe impl Send for Mapper {}
|
unsafe impl Send for Mapper {}
|
||||||
unsafe impl Sync for Mapper {}
|
unsafe impl Sync for Mapper {}
|
||||||
|
|
||||||
|
|
||||||
impl Clone for Mapper {
|
impl Clone for Mapper {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
mod layer_handler;
|
|
||||||
@ -47,3 +47,21 @@ pub enum PluginError {
|
|||||||
#[error("")]
|
#[error("")]
|
||||||
PluginError,
|
PluginError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum PoolError {
|
||||||
|
#[error("")]
|
||||||
|
TimestampError,
|
||||||
|
#[error("Data Pool is full")]
|
||||||
|
PoolFull,
|
||||||
|
#[error("Data Pool is not initialized")]
|
||||||
|
PoolInitialized(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RenderError {
|
||||||
|
#[error("")]
|
||||||
|
PreRenderError(#[from] femtovg::ErrorKind),
|
||||||
|
}
|
||||||
|
|||||||
@ -11,17 +11,13 @@ mod components;
|
|||||||
mod config;
|
mod config;
|
||||||
mod coords;
|
mod coords;
|
||||||
mod data;
|
mod data;
|
||||||
mod dealers;
|
|
||||||
mod dynamic_col;
|
|
||||||
mod errors;
|
mod errors;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod plugin_system;
|
mod plugin_system;
|
||||||
mod render;
|
|
||||||
mod timeline;
|
|
||||||
mod window;
|
|
||||||
use components::app::AppModel;
|
use components::app::AppModel;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
const APP_ID: &str = "org.tsuki.radar_g";
|
const APP_ID: &str = "org.tsuki.radar_g";
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,9 @@ use ndarray::parallel::prelude::*;
|
|||||||
use ndarray::{Array2, ArrayView2};
|
use ndarray::{Array2, ArrayView2};
|
||||||
use num_traits::{Num, AsPrimitive, FromPrimitive};
|
use num_traits::{Num, AsPrimitive, FromPrimitive};
|
||||||
pub mod offscreen_renderer;
|
pub mod offscreen_renderer;
|
||||||
mod utils;
|
pub mod utils;
|
||||||
|
pub mod pool;
|
||||||
|
pub mod render_pipeline;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::Mapper,
|
coords::Mapper,
|
||||||
|
|||||||
161
src/pipeline/pool.rs
Normal file
161
src/pipeline/pool.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
use crate::errors::PoolError;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
type PResult<T> = Result<T, PoolError>;
|
||||||
|
|
||||||
|
pub struct Pool<T> {
|
||||||
|
items: VecDeque<(i64, T)>,
|
||||||
|
current: Option<(i64, usize)>,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Pool<T> {
|
||||||
|
pub fn new(len: usize) -> Self {
|
||||||
|
Pool {
|
||||||
|
items: VecDeque::new(),
|
||||||
|
current: None,
|
||||||
|
len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self, timestamp: i64, item: T) -> PResult<()> {
|
||||||
|
let len = self.items.len();
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
self.items.push_back((timestamp, item));
|
||||||
|
self.current.replace((timestamp, 0));
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
return Err(PoolError::PoolInitialized("Pool is already initialized"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, item: T, timestamp: i64) -> PResult<()> {
|
||||||
|
let len = self.items.len();
|
||||||
|
if len == self.len {
|
||||||
|
return Err(PoolError::PoolFull);
|
||||||
|
}
|
||||||
|
if len == 0 {
|
||||||
|
return Err(PoolError::PoolInitialized("Pool is not initialized"));
|
||||||
|
}
|
||||||
|
if len == 1 {
|
||||||
|
if self.items[0].0 < timestamp {
|
||||||
|
self.items.push_back((timestamp, item));
|
||||||
|
} else {
|
||||||
|
self.items.push_front((timestamp, item));
|
||||||
|
self.current.replace((timestamp, 1));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let back = self
|
||||||
|
.items
|
||||||
|
.back()
|
||||||
|
.map(|(last_timestamp, _)| *last_timestamp < timestamp)
|
||||||
|
.unwrap();
|
||||||
|
let front = self
|
||||||
|
.items
|
||||||
|
.front()
|
||||||
|
.map(|(first_timestamp, _)| *first_timestamp > timestamp)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if !(back || front) {
|
||||||
|
return Err(PoolError::TimestampError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if back {
|
||||||
|
self.items.push_back((timestamp, item));
|
||||||
|
} else {
|
||||||
|
self.items.push_front((timestamp, item));
|
||||||
|
self.current.as_mut().map(|(_, index)| *index += 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&self) -> Option<&T> {
|
||||||
|
self.current
|
||||||
|
.map(|(_, index)| &self.items[index])
|
||||||
|
.map(|(_, item)| item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Option<&T> {
|
||||||
|
let len = self.items.len();
|
||||||
|
let current = self.current.as_mut().map(|(_, index)| index);
|
||||||
|
|
||||||
|
if let Some(index) = current {
|
||||||
|
if *index + 1 < len {
|
||||||
|
*index += 1;
|
||||||
|
self.current
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, index)| &self.items[*index])
|
||||||
|
.map(|(_, item)| item)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: isize) -> Option<&T> {
|
||||||
|
self.current
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, current_index)| *current_index as isize + index)
|
||||||
|
.and_then(|index| {
|
||||||
|
if index >= 0 && index < self.items.len() as isize {
|
||||||
|
Some(&self.items[index as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(_, item)| item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, index: isize) -> Option<&mut T> {
|
||||||
|
self.current
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, current_index)| *current_index as isize + index)
|
||||||
|
.and_then(|index| {
|
||||||
|
if index >= 0 && index < self.items.len() as isize {
|
||||||
|
Some(&mut self.items[index as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(_, item)| item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||||
|
self.items.iter().map(|(_, item)| item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||||
|
self.items.iter_mut().map(|(_, item)| item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_current(&mut self, timestamp: i64, item: T) -> PResult<()> {
|
||||||
|
if self.items.len() == 0 {
|
||||||
|
return Err(PoolError::PoolInitialized("Pool is not initialized"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let position = self.items.iter().position(|(time, _)| *time == timestamp);
|
||||||
|
|
||||||
|
if let Some(p) = position {
|
||||||
|
let start = (p - self.len / 2).max(0);
|
||||||
|
let end = (p + self.len / 2).min(self.items.len() - 1);
|
||||||
|
self.items = self.items.drain(start..end).collect();
|
||||||
|
self.current.replace((timestamp, self.len / 2));
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
self.items.clear();
|
||||||
|
self.init(timestamp, item);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.items.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/pipeline/render_pipeline.rs
Normal file
38
src/pipeline/render_pipeline.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use chrono::prelude::*;
|
||||||
|
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::widgets::{Target, TargetType};
|
||||||
|
|
||||||
|
use super::offscreen_renderer::CanvasWrapper;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RenderResult {
|
||||||
|
canvas: Arc<Mutex<CanvasWrapper>>,
|
||||||
|
img: Target,
|
||||||
|
time: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RenderResult {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut canvas = self.canvas.lock().unwrap();
|
||||||
|
if let TargetType::ImageId(img) = self.img.target {
|
||||||
|
canvas.delete_image(img);
|
||||||
|
}
|
||||||
|
let _ = self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderResult {
|
||||||
|
pub fn new(canvas: Arc<Mutex<CanvasWrapper>>, img: Target, time: DateTime<Utc>) -> Self {
|
||||||
|
Self { canvas, img, time }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timestamp(&self) -> i64 {
|
||||||
|
self.time.timestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&self) -> Target {
|
||||||
|
self.img.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,171 +1,116 @@
|
|||||||
// examples/common/mod.rs
|
use super::pool::Pool;
|
||||||
//
|
use super::render_pipeline::RenderResult;
|
||||||
// OpenGL convenience wrappers used in the examples.
|
use crate::errors::RenderError;
|
||||||
|
use chrono::{prelude::*, Duration};
|
||||||
use gl;
|
use regex::Regex;
|
||||||
use gl::types::{GLchar, GLenum, GLint, GLuint};
|
use smallvec::SmallVec;
|
||||||
use std::fs::File;
|
use std::{
|
||||||
use std::io::Read;
|
collections::{HashMap, VecDeque},
|
||||||
use std::os::raw::c_void;
|
future::Future,
|
||||||
use std::ptr;
|
pin::Pin,
|
||||||
use surfman::GLApi;
|
};
|
||||||
|
use tokio::sync::{oneshot, Mutex};
|
||||||
pub struct Program {
|
|
||||||
pub object: GLuint,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
vertex_shader: Shader,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fragment_shader: Shader,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Program {
|
|
||||||
pub fn new(vertex_shader: Shader, fragment_shader: Shader) -> Program {
|
|
||||||
unsafe {
|
|
||||||
let program = gl::CreateProgram();
|
|
||||||
ck();
|
|
||||||
gl::AttachShader(program, vertex_shader.object);
|
|
||||||
ck();
|
|
||||||
gl::AttachShader(program, fragment_shader.object);
|
|
||||||
ck();
|
|
||||||
gl::LinkProgram(program);
|
|
||||||
ck();
|
|
||||||
Program {
|
|
||||||
object: program,
|
|
||||||
vertex_shader,
|
|
||||||
fragment_shader,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Shader {
|
|
||||||
object: GLuint,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Shader {
|
|
||||||
pub fn new(
|
|
||||||
name: &str,
|
|
||||||
kind: ShaderKind,
|
|
||||||
gl_api: GLApi,
|
|
||||||
gl_texture_target: GLenum,
|
|
||||||
resource_loader: &dyn ResourceLoader,
|
|
||||||
) -> Shader {
|
|
||||||
let mut source = vec![];
|
|
||||||
match gl_api {
|
|
||||||
GLApi::GL => source.extend_from_slice(b"#version 330\n"),
|
|
||||||
GLApi::GLES => source.extend_from_slice(b"#version 300 es\n"),
|
|
||||||
}
|
|
||||||
match gl_texture_target {
|
|
||||||
gl::TEXTURE_2D => {}
|
|
||||||
gl::TEXTURE_RECTANGLE => source.extend_from_slice(b"#define SAMPLER_RECT\n"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
resource_loader.slurp(&mut source, &format!("{}.{}.glsl", name, kind.extension()));
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let shader = gl::CreateShader(kind.to_gl());
|
|
||||||
ck();
|
|
||||||
gl::ShaderSource(
|
|
||||||
shader,
|
|
||||||
1,
|
|
||||||
&(source.as_ptr() as *const GLchar),
|
|
||||||
&(source.len() as GLint),
|
|
||||||
);
|
|
||||||
ck();
|
|
||||||
gl::CompileShader(shader);
|
|
||||||
ck();
|
|
||||||
|
|
||||||
let mut compile_status = 0;
|
|
||||||
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut compile_status);
|
|
||||||
ck();
|
|
||||||
if compile_status != gl::TRUE as GLint {
|
|
||||||
let mut info_log_length = 0;
|
|
||||||
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut info_log_length);
|
|
||||||
let mut info_log = vec![0; info_log_length as usize + 1];
|
|
||||||
gl::GetShaderInfoLog(
|
|
||||||
shader,
|
|
||||||
info_log_length,
|
|
||||||
ptr::null_mut(),
|
|
||||||
info_log.as_mut_ptr() as *mut _,
|
|
||||||
);
|
|
||||||
eprintln!(
|
|
||||||
"Failed to compile shader:\n{}",
|
|
||||||
String::from_utf8_lossy(&info_log)
|
|
||||||
);
|
|
||||||
panic!("Shader compilation failed!");
|
|
||||||
}
|
|
||||||
debug_assert_eq!(compile_status, gl::TRUE as GLint);
|
|
||||||
|
|
||||||
Shader { object: shader }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Buffer {
|
|
||||||
pub object: GLuint,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Buffer {
|
|
||||||
pub fn from_data(data: &[u8]) -> Buffer {
|
|
||||||
unsafe {
|
|
||||||
let mut buffer = 0;
|
|
||||||
gl::GenBuffers(1, &mut buffer);
|
|
||||||
ck();
|
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, buffer);
|
|
||||||
ck();
|
|
||||||
gl::BufferData(
|
|
||||||
gl::ARRAY_BUFFER,
|
|
||||||
data.len() as isize,
|
|
||||||
data.as_ptr() as *const c_void,
|
|
||||||
gl::STATIC_DRAW,
|
|
||||||
);
|
|
||||||
ck();
|
|
||||||
Buffer { object: buffer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
||||||
pub enum ShaderKind {
|
|
||||||
Vertex,
|
|
||||||
Fragment,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShaderKind {
|
|
||||||
fn extension(self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ShaderKind::Vertex => "vs",
|
|
||||||
ShaderKind::Fragment => "fs",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_gl(self) -> GLenum {
|
|
||||||
match self {
|
|
||||||
ShaderKind::Vertex => gl::VERTEX_SHADER,
|
|
||||||
ShaderKind::Fragment => gl::FRAGMENT_SHADER,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResourceLoader {
|
|
||||||
fn slurp(&self, dest: &mut Vec<u8>, filename: &str);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct FilesystemResourceLoader;
|
|
||||||
|
|
||||||
impl ResourceLoader for FilesystemResourceLoader {
|
|
||||||
fn slurp(&self, dest: &mut Vec<u8>, filename: &str) {
|
|
||||||
let path = format!("resources/examples/{}", filename);
|
|
||||||
File::open(&path)
|
|
||||||
.expect("Failed to open file!")
|
|
||||||
.read_to_end(dest)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ck() {
|
pub fn ck() {
|
||||||
unsafe {
|
unsafe {
|
||||||
debug_assert_eq!(gl::GetError(), gl::NO_ERROR);
|
debug_assert_eq!(gl::GetError(), gl::NO_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DATATIME_FORMAT: regex::Regex = Regex::new(r"(?:%[YHMSmd](?:[-/:_]?%[YHMSmd])*)").unwrap();
|
||||||
|
|
||||||
|
pub struct Pipeline {
|
||||||
|
pool: VecDeque<Pin<Box<dyn Future<Output = Option<RenderResult>>>>>,
|
||||||
|
switcher: Pool<oneshot::Receiver<i32>>,
|
||||||
|
results: Mutex<SmallVec<[RenderResult; 20]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pipeline {
|
||||||
|
pub fn new(len: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
pool: VecDeque::new(),
|
||||||
|
switcher: Pool::new(len),
|
||||||
|
results: Mutex::new(SmallVec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) {
|
||||||
|
while let Some(task) = self.pool.pop_front() {
|
||||||
|
let res = task.await;
|
||||||
|
if let Some(res) = res {
|
||||||
|
self.results.lock().await.push(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_task(&mut self, timestamp: i64, task: Pin<Box<dyn Future<Output = RenderResult>>>) {
|
||||||
|
let (mut tx, rx) = oneshot::channel::<i32>();
|
||||||
|
|
||||||
|
let future = async move {
|
||||||
|
tokio::select! {
|
||||||
|
res = task => Some(res),
|
||||||
|
_ = tx.closed() => {
|
||||||
|
println!("task canceled");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.pool.push_back(Box::pin(future));
|
||||||
|
self.switcher.add(rx, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_task(&mut self, timestamp: i64) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dispatcher {
|
||||||
|
datetime: DateTime<Utc>,
|
||||||
|
path_format: HashMap<String, String>,
|
||||||
|
fore_len: usize,
|
||||||
|
back_len: usize,
|
||||||
|
step: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatcher {
|
||||||
|
pub fn new(fore_len: usize, back_len: usize, step: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
datetime: Utc::now(),
|
||||||
|
path_format: HashMap::new(),
|
||||||
|
fore_len,
|
||||||
|
back_len,
|
||||||
|
step,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_path_format(&mut self, formats: HashMap<String, String>) {
|
||||||
|
self.path_format = formats;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_path(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
current_time: DateTime<Utc>,
|
||||||
|
check_existed: bool,
|
||||||
|
) -> Option<String> {
|
||||||
|
self.path_format.get(name).map(|s| {
|
||||||
|
let mut path = s.clone();
|
||||||
|
let need_formated = DATATIME_FORMAT.captures_iter(&path);
|
||||||
|
|
||||||
|
let mut fore = self.fore_len;
|
||||||
|
let mut back = self.back_len;
|
||||||
|
for f in 1..fore + 1 {
|
||||||
|
let t = current_time - self.step * f as i32;
|
||||||
|
for need_format in need_formated {
|
||||||
|
let t = t.format(&need_format[0]).to_string();
|
||||||
|
path = path.replace(&need_format[0], &t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if check_existed {
|
||||||
|
if std::path::Path::new(&path).exists() {
|
||||||
|
fore = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use surfman::{
|
|||||||
GLVersion, NativeConnection, NativeContext, SurfaceAccess, SurfaceType,
|
GLVersion, NativeConnection, NativeContext, SurfaceAccess, SurfaceType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::render::predefined::color_mapper::BoundaryNorm;
|
use crate::widgets::render::predefined::color_mapper::BoundaryNorm;
|
||||||
|
|
||||||
pub fn meshgrid<T>(x: ArrayView1<T>, y: ArrayView1<T>) -> (Array2<T>, Array2<T>)
|
pub fn meshgrid<T>(x: ArrayView1<T>, y: ArrayView1<T>) -> (Array2<T>, Array2<T>)
|
||||||
where
|
where
|
||||||
|
|||||||
7
src/widgets/mod.rs
Normal file
7
src/widgets/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pub mod dynamic_col;
|
||||||
|
pub mod render;
|
||||||
|
pub mod timeline;
|
||||||
|
|
||||||
|
pub use dynamic_col::*;
|
||||||
|
pub use render::*;
|
||||||
|
pub use timeline::*;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
mod imp;
|
mod imp;
|
||||||
use crate::{coords::Range, render::Render};
|
use super::super::Render;
|
||||||
|
use crate::coords::Range;
|
||||||
use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path};
|
use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path};
|
||||||
use glib::subclass::types::ObjectSubclassIsExt;
|
use glib::subclass::types::ObjectSubclassIsExt;
|
||||||
|
|
||||||
317
src/widgets/render/imp.rs
Normal file
317
src/widgets/render/imp.rs
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
use super::exterior::ExteriorWidget;
|
||||||
|
use super::interior::InteriorWidget;
|
||||||
|
use super::{Layer, WindowCoord};
|
||||||
|
use crate::coords::proj::Mercator;
|
||||||
|
use crate::coords::Mapper;
|
||||||
|
use femtovg::{Canvas, Color, FontId, Renderer};
|
||||||
|
use gtk::glib;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
use gtk::traits::{GLAreaExt, WidgetExt};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
|
pub struct RenderConfig {
|
||||||
|
pub padding: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum RenderMotion {
|
||||||
|
Translate,
|
||||||
|
Scale,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RenderMotion {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RenderStatus {
|
||||||
|
pub(super) window_size: Option<(i32, i32)>,
|
||||||
|
pub(super) scale_rate: Option<f64>,
|
||||||
|
pub(super) pointer_location: WindowCoord,
|
||||||
|
pub(super) motion: RenderMotion,
|
||||||
|
pub(super) scale: f32,
|
||||||
|
pub(super) translate: Option<(f32, f32)>,
|
||||||
|
pub(super) view_range: Option<((f64, f64), (f64, f64))>,
|
||||||
|
translation: Option<(f64, f64)>,
|
||||||
|
init_translation: (f64, f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Render {
|
||||||
|
pub(super) exterior: RefCell<ExteriorWidget>,
|
||||||
|
pub(super) interior: RefCell<InteriorWidget>,
|
||||||
|
pub(super) canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
|
||||||
|
pub config: RefCell<RenderConfig>,
|
||||||
|
pub status: RefCell<RenderStatus>,
|
||||||
|
pub mapper: RefCell<Mapper>,
|
||||||
|
pub(super) interior_layers: RefCell<Vec<Layer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Render {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
exterior: RefCell::new(ExteriorWidget::default()),
|
||||||
|
interior: RefCell::new(InteriorWidget::default()),
|
||||||
|
interior_layers: RefCell::new(Vec::new()),
|
||||||
|
config: RefCell::new(RenderConfig::default()),
|
||||||
|
status: RefCell::new(RenderStatus::default()),
|
||||||
|
mapper: RefCell::new(Mercator::default().into()),
|
||||||
|
canvas: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for Render {
|
||||||
|
const NAME: &'static str = "Render";
|
||||||
|
type Type = super::Render;
|
||||||
|
type ParentType = gtk::GLArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for Render {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
let area = self.obj();
|
||||||
|
area.set_has_stencil_buffer(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for Render {}
|
||||||
|
|
||||||
|
impl GLAreaImpl for Render {
|
||||||
|
fn resize(&self, width: i32, height: i32) {
|
||||||
|
self.status.borrow_mut().window_size = Some((width, height));
|
||||||
|
self.ensure_canvas();
|
||||||
|
let mut canvas = self.canvas.borrow_mut();
|
||||||
|
let canvas = canvas.as_mut().unwrap();
|
||||||
|
canvas.set_size(
|
||||||
|
width as u32,
|
||||||
|
height as u32,
|
||||||
|
self.obj().scale_factor() as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
let translation = self.status.borrow().translation;
|
||||||
|
let scale_rate = self.status.borrow().scale_rate;
|
||||||
|
let mapper = self.mapper.borrow();
|
||||||
|
|
||||||
|
if let None = translation {
|
||||||
|
let mut status = self.status.borrow_mut();
|
||||||
|
status.translation = Some((mapper.get_bounds().0, mapper.get_bounds().2));
|
||||||
|
status.init_translation = (mapper.get_bounds().0, mapper.get_bounds().2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let None = scale_rate {
|
||||||
|
let scale = (mapper.get_bounds().3 - mapper.get_bounds().2) / canvas.height() as f64;
|
||||||
|
self.status.borrow_mut().scale_rate = Some(scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, context: >k::gdk::GLContext) -> bool {
|
||||||
|
self.ensure_canvas();
|
||||||
|
let configs = self.config.borrow();
|
||||||
|
let (w, h) = {
|
||||||
|
let mut canvas = self.canvas.borrow_mut();
|
||||||
|
let canvas = canvas.as_mut().unwrap();
|
||||||
|
|
||||||
|
let dpi = self.obj().scale_factor();
|
||||||
|
let w = canvas.width();
|
||||||
|
let h = canvas.height();
|
||||||
|
|
||||||
|
canvas.clear_rect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(w as i32 * dpi) as u32,
|
||||||
|
(h as i32 * dpi) as u32,
|
||||||
|
Color::rgba(0, 0, 0, 255),
|
||||||
|
);
|
||||||
|
(w, h)
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_range = self.status.borrow().view_range.clone();
|
||||||
|
if let Some(((lat1, lat2), (lon1, lon2))) = render_range {
|
||||||
|
let mut status = self.status.borrow_mut();
|
||||||
|
|
||||||
|
let mapper = self.mapper.borrow();
|
||||||
|
|
||||||
|
let (tx, ty) = mapper.map((lon1, lat1)).unwrap();
|
||||||
|
status.translation.replace((tx, ty));
|
||||||
|
|
||||||
|
let (lon1, lat1) = mapper.map((lon1, lat1)).unwrap();
|
||||||
|
let (lon2, lat2) = mapper.map((lon2, lat2)).unwrap();
|
||||||
|
|
||||||
|
let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64);
|
||||||
|
status.scale_rate.replace(scale);
|
||||||
|
|
||||||
|
status.view_range = None;
|
||||||
|
} else {
|
||||||
|
let mut status = self.status.borrow_mut();
|
||||||
|
match status.motion {
|
||||||
|
RenderMotion::Translate => {
|
||||||
|
if let Some((x, y)) = status.translate {
|
||||||
|
let (ix, iy) = status.init_translation;
|
||||||
|
status.translation = Some((
|
||||||
|
ix + x as f64 * status.scale_rate.unwrap(),
|
||||||
|
iy + y as f64 * status.scale_rate.unwrap(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
status.init_translation = status.translation.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RenderMotion::Scale => {
|
||||||
|
let scale_rate = status.scale_rate.unwrap();
|
||||||
|
let scale_flag = status.scale as f64;
|
||||||
|
let step = scale_rate * 0.1;
|
||||||
|
|
||||||
|
let (tx, ty) = status.translation.unwrap();
|
||||||
|
let (px, py) = status.pointer_location;
|
||||||
|
|
||||||
|
let scaled = scale_rate + scale_flag * step;
|
||||||
|
status.scale_rate = Some(scaled);
|
||||||
|
let sx = scale_flag * step * px as f64;
|
||||||
|
let sy = scale_flag * step * py as f64;
|
||||||
|
status.translation = Some((tx - sx, ty - sy));
|
||||||
|
status.init_translation = status.translation.unwrap();
|
||||||
|
}
|
||||||
|
RenderMotion::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = &(*self.interior_layers.borrow());
|
||||||
|
self.interior
|
||||||
|
.borrow()
|
||||||
|
.draw(c, &self.obj(), self.status.borrow(), configs);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut canvas = self.canvas.borrow_mut();
|
||||||
|
let canvas = canvas.as_mut().unwrap();
|
||||||
|
self.exterior.borrow().draw(canvas, &self.obj());
|
||||||
|
canvas.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render {
|
||||||
|
fn ensure_canvas(&self) {
|
||||||
|
use femtovg::{renderer, Canvas};
|
||||||
|
use glow::HasContext;
|
||||||
|
|
||||||
|
if self.canvas.borrow().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let widget = self.obj();
|
||||||
|
widget.attach_buffers();
|
||||||
|
|
||||||
|
static LOAD_FN: fn(&str) -> *const std::ffi::c_void =
|
||||||
|
|s| epoxy::get_proc_addr(s) as *const _;
|
||||||
|
// SAFETY: Need to get the framebuffer id that gtk expects us to draw into, so femtovg
|
||||||
|
// knows which framebuffer to bind. This is safe as long as we call attach_buffers
|
||||||
|
// beforehand. Also unbind it here just in case, since this can be called outside render.
|
||||||
|
let (mut renderer, fbo) = unsafe {
|
||||||
|
let renderer =
|
||||||
|
renderer::OpenGl::new_from_function(LOAD_FN).expect("Cannot create renderer");
|
||||||
|
let ctx = glow::Context::from_loader_function(LOAD_FN);
|
||||||
|
let id = NonZeroU32::new(ctx.get_parameter_i32(glow::DRAW_FRAMEBUFFER_BINDING) as u32)
|
||||||
|
.expect("No GTK provided framebuffer binding");
|
||||||
|
ctx.bind_framebuffer(glow::FRAMEBUFFER, None);
|
||||||
|
(renderer, glow::NativeFramebuffer(id))
|
||||||
|
};
|
||||||
|
renderer.set_screen_target(Some(fbo));
|
||||||
|
let mut canvas = Canvas::new(renderer).expect("Cannot create canvas");
|
||||||
|
canvas
|
||||||
|
.add_font_dir(std::path::Path::new("./src/assets"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.canvas.replace(Some(canvas));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn window_size(&self) -> Option<(i32, i32)> {
|
||||||
|
self.status.borrow().window_size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn view_range(&self) -> Option<((f64, f64), (f64, f64))> {
|
||||||
|
let padding = self.padding();
|
||||||
|
let (w, h) = self.window_size().unwrap();
|
||||||
|
let (w, h) = (
|
||||||
|
w as f32 - padding[1] - padding[3],
|
||||||
|
h as f32 - padding[0] - padding[2],
|
||||||
|
);
|
||||||
|
let (w, h) = (w as f64, h as f64);
|
||||||
|
|
||||||
|
let mapper = self.mapper.borrow();
|
||||||
|
|
||||||
|
let status = self.status.borrow();
|
||||||
|
status.translation.and_then(|(tx, ty)| {
|
||||||
|
status.scale_rate.and_then(|scale| {
|
||||||
|
let (x1, y1) = (tx + padding[3] as f64, ty + padding[2] as f64);
|
||||||
|
let (x2, y2) = (
|
||||||
|
tx + w * scale + padding[3] as f64,
|
||||||
|
ty + h * scale + padding[2] as f64,
|
||||||
|
);
|
||||||
|
let (x1, y1) = mapper.inverse_map((x1, y1)).unwrap();
|
||||||
|
let (x2, y2) = mapper.inverse_map((x2, y2)).unwrap();
|
||||||
|
Some(((x1, x2), (y1, y2)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> {
|
||||||
|
let (x, y) = loc;
|
||||||
|
let status = self.status.borrow();
|
||||||
|
status.translation.and_then(|(tx, ty)| {
|
||||||
|
status.scale_rate.and_then(|scale| {
|
||||||
|
let (x, y) = (x as f64, y as f64);
|
||||||
|
Some((tx + x * scale, (ty + y * scale)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn padding(&self) -> [f32; 4] {
|
||||||
|
self.config.borrow().padding
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> {
|
||||||
|
let (x, y) = loc;
|
||||||
|
let (_, h) = self.window_size().unwrap();
|
||||||
|
let status = self.status.borrow();
|
||||||
|
status.translation.and_then(|(tx, ty)| {
|
||||||
|
status.scale_rate.and_then(|scale| {
|
||||||
|
Some((
|
||||||
|
(x - tx as f64) as f32 / scale as f32,
|
||||||
|
h as f32 - (y - ty as f64) as f32 / scale as f32,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_translation(&self, translation: (f64, f64)) {
|
||||||
|
let mut status = self.status.borrow_mut();
|
||||||
|
status.translation = Some(translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pointer_loc(&self) -> (f32, f32) {
|
||||||
|
let (x, y) = self.status.borrow().pointer_location.clone();
|
||||||
|
let (_, h) = self.window_size().unwrap();
|
||||||
|
(x, h as f32 - y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_view(&self, range: (f64, f64, f64, f64)) {
|
||||||
|
let (lat1, lat2, lon1, lon2) = range;
|
||||||
|
self.status
|
||||||
|
.borrow_mut()
|
||||||
|
.view_range
|
||||||
|
.replace(((lat1, lat2), (lon1, lon2)));
|
||||||
|
// if let Some((w, h)) = self.window_size() {
|
||||||
|
// println!("w:{}, h:{}", w, h);
|
||||||
|
// let scale = ((lat1 - lat2).abs() / h as f64).max((lon1 - lon2).abs() / w as f64);
|
||||||
|
// self.status.borrow_mut().scale_rate.replace(scale);
|
||||||
|
// self.set_translation((lat1, lon1));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::render::WindowCoord;
|
use super::super::WindowCoord;
|
||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
|
use super::super::{cms::CMS, Render};
|
||||||
|
use crate::coords::Range;
|
||||||
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
||||||
use crate::render::cms::CMS;
|
|
||||||
use crate::{coords::Range, render::Render};
|
|
||||||
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
use femtovg::{renderer::OpenGl, Canvas, ImageId};
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Ref, RefCell},
|
cell::{Ref, RefCell},
|
||||||
@ -1,11 +1,11 @@
|
|||||||
mod imp;
|
mod imp;
|
||||||
mod layers;
|
mod layers;
|
||||||
use crate::render::Render;
|
use super::super::Render;
|
||||||
use femtovg::{renderer::OpenGl, Canvas};
|
use femtovg::{renderer::OpenGl, Canvas};
|
||||||
pub use layers::{Layer, LayerImpl, LayerImplSync, Target, TargetType};
|
pub use layers::{Layer, LayerImpl, LayerImplSync, Target, TargetType};
|
||||||
use std::cell::Ref;
|
use std::cell::Ref;
|
||||||
|
|
||||||
use crate::render::imp::{RenderConfig, RenderStatus};
|
use super::imp::{RenderConfig, RenderStatus};
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub struct InteriorWidget(ObjectSubclass<imp::InteriorWidget>);
|
pub struct InteriorWidget(ObjectSubclass<imp::InteriorWidget>);
|
||||||
@ -16,7 +16,7 @@ use gtk::{EventControllerScrollFlags, Inhibit};
|
|||||||
pub use interior::*;
|
pub use interior::*;
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{Ref, RefMut};
|
||||||
|
|
||||||
pub(super) type WindowCoord = (f32, f32);
|
pub type WindowCoord = (f32, f32);
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub struct Render(ObjectSubclass<imp::Render>)
|
pub struct Render(ObjectSubclass<imp::Render>)
|
||||||
@ -2,8 +2,8 @@ use crate::coords::Range;
|
|||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use super::super::{Layer, LayerImpl, Render};
|
||||||
use crate::coords::Mapper;
|
use crate::coords::Mapper;
|
||||||
use crate::render::{Layer, LayerImpl, Render};
|
|
||||||
use femtovg::renderer::OpenGl;
|
use femtovg::renderer::OpenGl;
|
||||||
use femtovg::{Canvas, Paint};
|
use femtovg::{Canvas, Paint};
|
||||||
use geo_types::LineString;
|
use geo_types::LineString;
|
||||||
@ -10,11 +10,8 @@ use num_traits::{Num, NumOps};
|
|||||||
use std::{fmt::Debug, io::Cursor, marker::PhantomData};
|
use std::{fmt::Debug, io::Cursor, marker::PhantomData};
|
||||||
|
|
||||||
use super::super::renders::DataRenderer;
|
use super::super::renders::DataRenderer;
|
||||||
use crate::{
|
use super::super::{cms::CMS, LayerImpl, Render, Target, TargetType};
|
||||||
data::Radar2d,
|
use crate::{data::Radar2d, utils::meshgrid};
|
||||||
render::{cms::CMS, LayerImpl, Render, Target, TargetType},
|
|
||||||
utils::meshgrid,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GridFieldRenderer<CMAP, T>
|
pub struct GridFieldRenderer<CMAP, T>
|
||||||
@ -206,7 +203,7 @@ where
|
|||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
|
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
|
||||||
cms: crate::render::cms::CMS,
|
cms: CMS,
|
||||||
) -> Option<Target> {
|
) -> Option<Target> {
|
||||||
let result = self
|
let result = self
|
||||||
.renderer
|
.renderer
|
||||||
@ -6,14 +6,9 @@ use std::path::Path;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use super::super::{cms::CMS, Layer, LayerImpl, LayerImplSync, Render, Target, TargetType};
|
||||||
|
use crate::data::{AsyncDataLoader, DataLoader, Radar2d};
|
||||||
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
use crate::pipeline::offscreen_renderer::CanvasWrapper;
|
||||||
use crate::render::cms::CMS;
|
|
||||||
use crate::render::{LayerImpl, Target};
|
|
||||||
use crate::render::{LayerImplSync, TargetType};
|
|
||||||
use crate::{
|
|
||||||
data::{AsyncDataLoader, DataLoader, Radar2d},
|
|
||||||
render::{Layer, Render},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
color_mapper::ColorMapper,
|
color_mapper::ColorMapper,
|
||||||
@ -1,50 +0,0 @@
|
|||||||
use glib::subclass::InitializingObject;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
use gtk::subclass::prelude::*;
|
|
||||||
use gtk::{glib, Button, CompositeTemplate};
|
|
||||||
|
|
||||||
#[derive(CompositeTemplate, Default)]
|
|
||||||
#[template(resource = "/org/cinrad_g/window.ui")]
|
|
||||||
pub struct Window {
|
|
||||||
#[template_child]
|
|
||||||
pub button: TemplateChild<Button>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for Window {
|
|
||||||
// `NAME` needs to match `class` attribute of template
|
|
||||||
const NAME: &'static str = "MyGtkAppWindow";
|
|
||||||
type Type = super::Window;
|
|
||||||
type ParentType = gtk::ApplicationWindow;
|
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
|
||||||
klass.bind_template();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_init(obj: &InitializingObject<Self>) {
|
|
||||||
obj.init_template();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for Window {
|
|
||||||
fn constructed(&self) {
|
|
||||||
// Call "constructed" on parent
|
|
||||||
self.parent_constructed();
|
|
||||||
|
|
||||||
// Connect to "clicked" signal of `button`
|
|
||||||
self.button.connect_clicked(move |button| {
|
|
||||||
// Set the label to "Hello World!" after the button has been clicked on
|
|
||||||
button.set_label("Hello World!");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ANCHOR_END: object_impl
|
|
||||||
|
|
||||||
// Trait shared by all widgets
|
|
||||||
impl WidgetImpl for Window {}
|
|
||||||
|
|
||||||
// Trait shared by all windows
|
|
||||||
impl WindowImpl for Window {}
|
|
||||||
|
|
||||||
// Trait shared by all application windows
|
|
||||||
impl ApplicationWindowImpl for Window {}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
mod imp;
|
|
||||||
|
|
||||||
use glib::Object;
|
|
||||||
use gtk::{gio, glib, Application};
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct Window(ObjectSubclass<imp::Window>)
|
|
||||||
@extends gtk::ApplicationWindow, gtk::Window, gtk::Widget,
|
|
||||||
@implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable,
|
|
||||||
gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new(app: &Application) -> Self {
|
|
||||||
// Create new window
|
|
||||||
Object::builder()
|
|
||||||
.property("application", app)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user