This commit is contained in:
Tsuki 2024-02-22 11:41:28 +08:00
parent 01d14f5f4e
commit c83a9e6d8a
10 changed files with 214 additions and 78 deletions

7
Cargo.lock generated
View File

@ -433,6 +433,12 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "glib-build-tools"
version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a65d79efe318ef2cbbbb37032b125866fd82c34ea44c816132621bbc552e716"
[[package]] [[package]]
name = "glib-macros" name = "glib-macros"
version = "0.17.10" version = "0.17.10"
@ -971,6 +977,7 @@ name = "rsproject"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"glib-build-tools",
"gtk4", "gtk4",
"libadwaita", "libadwaita",
"relm4", "relm4",

View File

@ -12,3 +12,6 @@ relm4 = { version = "0.6.2", features = ["libadwaita"]}
relm4-icons = { version = "0.6.0", features = ["add-filled"] } relm4-icons = { version = "0.6.0", features = ["add-filled"] }
chrono = "0.4.34" chrono = "0.4.34"
tracker = "0.2.1" tracker = "0.2.1"
[build-dependencies]
glib-build-tools = "0.17.0"

8
build.rs Normal file
View File

@ -0,0 +1,8 @@
fn main() {
glib_build_tools::compile_resources(
&["data"],
// "src/resources/resources.gresource.xml",
"data/css.gresource.xml",
"css.gresource",
);
}

6
data/css.gresource.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="org/tsuki/rsproject/css/">
<file alias="main.css">css/main.css</file>
</gresource>
</gresources>

34
data/css/main.css Normal file
View File

@ -0,0 +1,34 @@
.rb {
border-radius: 10px;
}
paned>separator {
color: transparent;
}
.h1 {
font-size: 25px;
font-weight: bold;
line-height: 1.25;
}
.h2 {
font-size: 20px;
font-weight: bold;
line-height: 1.25;
}
.h3 {
font-size: 15px;
font-weight: bold;
line-height: 1.25;
color: #666;
}
.content {
font-size: 14px;
}
.lv {
background: transparent;
}

View File

@ -12,6 +12,7 @@ use std::{
rc::Rc, rc::Rc,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use gtk::Widget;
relm4::new_action_group!(FileActionGroup, "file"); relm4::new_action_group!(FileActionGroup, "file");
relm4::new_stateless_action!(OpenAction, FileActionGroup, "open"); relm4::new_stateless_action!(OpenAction, FileActionGroup, "open");
@ -45,27 +46,33 @@ impl Component for AppModel {
set_default_height: 900, set_default_height: 900,
set_focus_on_click:true, set_focus_on_click:true,
connect_close_request[sender] => move |_| { connect_close_request[sender] => move |_| {
let app = relm4::main_application();
app.quit();
gtk::Inhibit(true) gtk::Inhibit(true)
}, },
gtk::Box{ gtk::Box{
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_valign:gtk::Align::Fill, set_valign:gtk::Align::Fill,
set_spacing:2, set_spacing:2,
gtk::HeaderBar{}, // gtk::HeaderBar{},
gtk::Box{ gtk::Box{
set_hexpand: true, set_hexpand: true,
set_vexpand: true, set_vexpand: true,
#[name="stack"] #[name="stack"]
gtk::Stack{ adw::NavigationView{
set_transition_type:gtk::StackTransitionType::SlideUp,
set_transition_duration:300,
set_hexpand:true, set_hexpand:true,
set_vexpand:true, set_vexpand:true,
}, },
} }
} }
}, },
home_page = adw::Clamp{ home_page = adw::NavigationPage{
#[wrap(Some)]
set_child=&adw::ToolbarView{
add_top_bar=&adw::HeaderBar{},
#[wrap(Some)]
set_content=&adw::Clamp{
set_maximum_size: 1000, set_maximum_size: 1000,
gtk::Box{ gtk::Box{
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
@ -84,9 +91,21 @@ impl Component for AppModel {
}, },
}, },
} }
}
}
},
new_page = adw::NavigationPage{
set_title: "New Project",
set_tag: Some("new_page"),
#[wrap(Some)]
set_child=&adw::ToolbarView{
add_top_bar=&adw::HeaderBar{},
#[wrap(Some)]
set_content=model.new_page_model.widget() -> &adw::NavigationSplitView {}
}
}, },
home_stack_page = stack.add_titled(&home_page, Some("home"), "Home") -> gtk::StackPage{}, stack.add(&home_page),
new_project_page = stack.add_titled(model.new_page_model.widget(),Some("new_page"), "New") -> gtk::StackPage{}, stack.add(&new_page),
} }
menu! { menu! {
@ -137,7 +156,8 @@ impl Component for AppModel {
) { ) {
match msg { match msg {
AppMsg::NewProject => { AppMsg::NewProject => {
widgets.stack.set_visible_child_name("new_page"); // widgets.stack.set_visible_child_name("new_page");
widgets.stack.push_by_tag("new_page");
} }
} }
} }

View File

@ -1,25 +1,33 @@
use crate::components::setting_item::{SettingItem, SettingType};
use crate::config::CommonConfig;
use adw::prelude::*; use adw::prelude::*;
use gtk::glib::clone; use gtk::glib::clone;
use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, ToggleButtonExt}; use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, ToggleButtonExt};
use relm4::{prelude::*, view};
use std::path::PathBuf;
use std::rc::Rc;
use gtk::ResponseType::No; use gtk::ResponseType::No;
use gtk::Widget; use gtk::Widget;
use relm4::factory::FactoryVecDeque; use relm4::factory::FactoryVecDeque;
use crate::components::setting_item::{SettingItem, SettingType}; use relm4::{prelude::*, view};
use crate::config::CommonConfig; use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
#[tracker::track]
#[derive(Debug)] #[derive(Debug)]
pub struct NewPageModel { pub struct NewPageModel {
common_setting: CommonConfig project_name: String,
common_setting: CommonConfig,
}
#[derive(Debug)]
pub enum NewPageMsg{
ChangeName(String)
} }
#[relm4::component(pub)] #[relm4::component(pub)]
impl SimpleComponent for NewPageModel { impl SimpleComponent for NewPageModel {
type Init = (); type Init = ();
type Output = (); type Output = ();
type Input = (); type Input = NewPageMsg;
view! { view! {
#[root] #[root]
@ -46,11 +54,28 @@ impl SimpleComponent for NewPageModel {
common_setting = gtk::Box{ common_setting = gtk::Box{
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
set_valign: gtk::Align::Center,
adw::PreferencesPage{ adw::PreferencesPage{
set_title: "Common", set_title: "Common",
add=&adw::PreferencesGroup{ add=&adw::PreferencesGroup{
add=&gtk::Label {
set_halign: gtk::Align::Start,
#[track = "model.changed(NewPageModel::project_name())"]
set_text: &model.project_name,
add_css_class: "h1"
}
},
add=&adw::PreferencesGroup{
set_title: "Common",
add=&adw::EntryRow{
set_title: "Project Name",
connect_text_notify[sender] => move |s| {
sender.input(NewPageMsg::ChangeName(s.text().to_string()));
}
},
#[iterate] #[iterate]
add: model.common_setting.to_settings().iter_mut().map(|v| v.to_widget()).collect::<Vec<_>>().iter() add: model.common_setting.to_settings().iter_mut().map(|v| v.widget()).collect::<Vec<_>>().iter()
} }
} }
}, },
@ -64,10 +89,21 @@ impl SimpleComponent for NewPageModel {
sender: relm4::ComponentSender<Self>, sender: relm4::ComponentSender<Self>,
) -> relm4::ComponentParts<Self> { ) -> relm4::ComponentParts<Self> {
let config = CommonConfig::default(); let config = CommonConfig::default();
let model = NewPageModel { common_setting: config }; let mut model = NewPageModel {
project_name: format!("Project {}", 0),
common_setting: config,
tracker: 0,
};
let widgets = view_output!(); let widgets = view_output!();
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>) {
self.reset();
match msg {
NewPageMsg::ChangeName(name) => {
self.set_project_name(name);
}
}
}
} }

View File

@ -1,7 +1,8 @@
use std::cell::{Cell, RefCell};
use adw::prelude::*; use adw::prelude::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{StringList, Widget}; use gtk::{StringList, Widget};
use relm4::{factory::FactoryView, gtk, prelude::*, FactorySender}; use relm4::{factory::FactoryView, gtk, prelude::*, FactorySender, RelmObjectExt};
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
@ -11,11 +12,11 @@ pub enum Msg {}
pub enum OutputMsg {} pub enum OutputMsg {}
pub enum SettingType { pub enum SettingType {
Select(Vec<String>), Select(Vec<String>, Option<Rc<RefCell<String>>>),
Action, Action,
Entry(Option<String>, Option<Box<dyn Fn(&str) -> bool>>), Entry(Option<String>, Option<Box<dyn Fn(&str) -> bool>>, Option<Rc<RefCell<String>>>),
Switch(bool), Switch(bool, Option<Rc<Cell<bool>>>),
Spin(f64, f64, f64, f64), Spin(f64, f64, f64, f64, Option<Rc<Cell<f64>>>),
} }
pub struct SettingItem { pub struct SettingItem {
@ -27,10 +28,17 @@ impl SettingItem {
pub fn new(title: String, _type: SettingType) -> Self { pub fn new(title: String, _type: SettingType) -> Self {
Self { title, _type } Self { title, _type }
} }
pub fn to_widget(&mut self) -> Widget { pub fn widget(&mut self) -> Widget {
(match &mut self._type { (match &mut self._type {
SettingType::Select(selects) => { SettingType::Select(selects, selected) => {
let w = adw::ComboRow::builder().title(&self.title).build(); let w = adw::ComboRow::builder().title(&self.title).build();
if let Some(selected) = selected {
let selected = selected.clone();
w.connect_selected_item_notify(|s| {
// println!("{}", text);
// selected.set(text.to_string());
});
}
let model = StringList::new( let model = StringList::new(
selects selects
.iter() .iter()
@ -45,37 +53,54 @@ impl SettingItem {
.title(&self.title) .title(&self.title)
.build() .build()
.upcast::<Widget>(), .upcast::<Widget>(),
SettingType::Entry(text, f) => { SettingType::Entry(text, f, buffer) => {
let w = adw::EntryRow::new(); let w = adw::EntryRow::new();
w.set_title(&self.title); w.set_title(&self.title);
let f = f.take();
if let Some(f) = f {
println!("test");
w.connect_text_notify(move |s| {
let text = s.text();
if !f(&text) {
s.set_text("");
}
});
}
if let Some(text) = text { if let Some(text) = text {
w.set_text(text); w.set_text(text);
} }
let f = f.take();
let buffer = buffer.clone();
if let Some(buffer) = buffer {
w.connect_text_notify(move |s| {
let text = s.text();
if let Some(f) = f.as_ref(){
if !f(&text) {
s.set_text("");
return;
}
}
*buffer.borrow_mut() = text.to_string();
});
}
w.upcast::<Widget>() w.upcast::<Widget>()
} }
SettingType::Switch(t) => { SettingType::Switch(t, v) => {
let w = adw::SwitchRow::new(); let w = adw::SwitchRow::new();
w.set_title(&self.title); w.set_title(&self.title);
w.set_active(*t); w.set_active(*t);
if let Some(v) = v {
let v = v.clone();
w.connect_active_notify(move |s| {
v.set(s.is_active());
});
}
w.upcast::<Widget>() w.upcast::<Widget>()
} }
SettingType::Spin(min, max, clamb, value) => { SettingType::Spin(min, max, clamb, value, v) => {
let w = adw::SpinRow::new( let w = adw::SpinRow::new(
Some(&gtk::Adjustment::new(*value, *min, *max, *clamb, 0.0, 0.0)), Some(&gtk::Adjustment::new(*value, *min, *max, *clamb, 0.0, 0.0)),
*clamb, *clamb,
0, 0,
); );
w.set_numeric(true); w.set_numeric(true);
if let Some(v) = v {
let v = v.clone();
w.connect_value_notify(move |s| {
v.set(s.value());
});
}
// w.set_range(*min, *max); // w.set_range(*min, *max);
w.set_value(*value); w.set_value(*value);
w.set_title(&self.title); w.set_title(&self.title);

View File

@ -1,15 +1,18 @@
use std::cell::{Cell, Ref, RefCell};
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
use crate::components::{SettingItem, SettingType}; use crate::components::{SettingItem, SettingType};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default, PartialOrd, PartialEq)]
pub struct CommonConfig{ pub struct CommonConfig{
pub name: String, pub name: Rc<RefCell<String>>,
pub version: String, pub version: Rc<RefCell<String>>,
radar_lens: Rc<Cell<f64>>,
pub radars: Vec<RadarConfig>, pub radars: Vec<RadarConfig>,
pub algorithms: Vec<AlgorithmConfig>, pub algorithms: Vec<AlgorithmConfig>,
} }
#[derive( Clone, Debug)] #[derive( Clone, Debug, PartialOrd, PartialEq)]
pub struct RadarConfig{ pub struct RadarConfig{
pub name: String, pub name: String,
pub _type: String, pub _type: String,
@ -21,35 +24,28 @@ pub struct RadarConfig{
r_limit: f64, r_limit: f64,
} }
#[derive( Clone, Debug)] #[derive( Clone, Debug, PartialOrd, PartialEq)]
pub struct AlgorithmConfig { pub struct AlgorithmConfig {
name: String, name: String,
version: String, version: String,
info: HashMap<String, String> // info: HashMap<String, String>
} }
impl CommonConfig{ impl CommonConfig{
pub fn to_settings(&self) -> Vec<SettingItem>{ pub fn to_settings(&self) -> Vec<SettingItem>{
let mut settings = Vec::new(); let mut settings = Vec::new();
settings.push(SettingItem::new("Name".to_string(), SettingType::Entry(Some(self.name.clone()), None))); // settings.push(SettingItem::new("Name".to_string(), SettingType::Entry(Some(self.name.borrow().clone()), None, Some(self.name.clone()))));
settings.push(SettingItem::new("Version".to_string(), SettingType::Entry(Some(self.version.clone()), None))); settings.push(SettingItem::new("Version".to_string(), SettingType::Entry(Some(self.version.borrow().clone()), None, Some(self.version.clone()))));
settings.push(SettingItem::new("Radar Num".to_string(), SettingType::Spin(0.0, 0.0, 100.0, 1.0))); settings.push(SettingItem::new("Radar Num".to_string(), SettingType::Spin(0.0, 0.0, 100.0, 1.0, Some(self.radar_lens.clone()))));
for radar in self.radars.iter(){ for radar in self.radars.iter(){
settings.push(SettingItem::new("Name".to_string(), SettingType::Entry(Some(radar.name.clone()), None))); settings.push(SettingItem::new("Name".to_string(), SettingType::Entry(Some(radar.name.clone()), None, None)));
settings.push(SettingItem::new("Type".to_string(), SettingType::Entry(Some(radar._type.clone()), None))); settings.push(SettingItem::new("Type".to_string(), SettingType::Entry(Some(radar._type.clone()), None, None)));
settings.push(SettingItem::new("Location".to_string(), SettingType::Entry(Some(format!("{},{},{}", radar.loc[0], radar.loc[1], radar.loc[2])), None))); settings.push(SettingItem::new("Location".to_string(), SettingType::Entry(Some(format!("{},{},{}", radar.loc[0], radar.loc[1], radar.loc[2])), None, None)));
settings.push(SettingItem::new("Azimuth Beam Width".to_string(), SettingType::Entry(Some(radar.az_beam_width.to_string()), None))); settings.push(SettingItem::new("Azimuth Beam Width".to_string(), SettingType::Entry(Some(radar.az_beam_width.to_string()), None, None)));
settings.push(SettingItem::new("Elevation Beam Width".to_string(), SettingType::Entry(Some(radar.el_beam_width.to_string()), None))); settings.push(SettingItem::new("Elevation Beam Width".to_string(), SettingType::Entry(Some(radar.el_beam_width.to_string()), None, None)));
settings.push(SettingItem::new("Azimuth Method".to_string(), SettingType::Select(vec!["1".to_string(), "2".to_string(), "3".to_string()]))); settings.push(SettingItem::new("Azimuth Method".to_string(), SettingType::Select(vec!["1".to_string(), "2".to_string(), "3".to_string()], None)));
settings.push(SettingItem::new("Elevation Method".to_string(), SettingType::Select(vec!["1".to_string(), "2".to_string(), "3".to_string()]))); settings.push(SettingItem::new("Elevation Method".to_string(), SettingType::Select(vec!["1".to_string(), "2".to_string(), "3".to_string()], None)));
settings.push(SettingItem::new("Range Limit".to_string(), SettingType::Entry(Some(radar.r_limit.to_string()), None))); settings.push(SettingItem::new("Range Limit".to_string(), SettingType::Entry(Some(radar.r_limit.to_string()), None, None)));
}
for algorithm in self.algorithms.iter(){
settings.push(SettingItem::new("Name".to_string(), SettingType::Entry(Some(algorithm.name.clone()), None)));
settings.push(SettingItem::new("Version".to_string(), SettingType::Entry(Some(algorithm.version.clone()), None)));
for (k, v) in algorithm.info.iter(){
settings.push(SettingItem::new(k.clone(), SettingType::Entry(Some(v.clone()), None)));
}
} }
settings settings
} }

View File

@ -9,26 +9,27 @@ mod components;
mod config; mod config;
use components::AppModel; use components::AppModel;
const APP_ID: &str = "org.tsuki.radar_g"; const APP_ID: &str = "org.tsuki.rsproject";
fn main() { fn main() {
// Load GL pointers from epoxy (GL context management library used by GTK). // Load GL pointers from epoxy (GL context management library used by GTK).
let relm = relm4::RelmApp::new(APP_ID); let relm = relm4::RelmApp::new(APP_ID);
initialize_custom_css();
relm.run::<AppModel>(()); relm.run::<AppModel>(());
// initialize_custom_css();
} }
// fn initialize_custom_css() { fn initialize_custom_css() {
// gio::resources_register_include!("css.gresource").unwrap(); gio::resources_register_include!("css.gresource").unwrap();
// // Load the CSS file and add it to the provider // Load the CSS file and add it to the provider
// let provider = gtk::CssProvider::new(); let provider = gtk::CssProvider::new();
// // provider.load_from_string(); // provider.load_from_string();
// provider.load_from_resource("/org/tsuki/radar_g/css/main.css"); provider.load_from_resource("/org/tsuki/rsproject/css/main.css");
// use gtk::gdk::Display; use gtk::gdk::Display;
// // Add the provider to the default screen // Add the provider to the default screen
// gtk::style_context_add_provider_for_display( gtk::style_context_add_provider_for_display(
// &Display::default().expect("Could not connect to a display."), &Display::default().expect("Could not connect to a display."),
// &provider, &provider,
// gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
// ); );
// } }