timeline click
This commit is contained in:
parent
24d91b628e
commit
1014062db7
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -403,16 +403,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.1",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -422,6 +422,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"cairo-rs",
|
||||
"chrono",
|
||||
"crossbeam",
|
||||
"epoxy",
|
||||
"euclid",
|
||||
|
||||
@ -65,6 +65,7 @@ surfman = "0.8.1"
|
||||
euclid = "0.22.9"
|
||||
gl = "0.14.0"
|
||||
crossbeam = "0.8.4"
|
||||
chrono = "0.4.32"
|
||||
# plotters-cairo = "0.5.0"
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@ use relm4::*;
|
||||
use relm4_components::open_button::{OpenButton, OpenButtonSettings};
|
||||
use relm4_components::open_dialog::OpenDialogSettings;
|
||||
use std::path::PathBuf;
|
||||
use chrono::{DateTime, Utc, Duration};
|
||||
|
||||
|
||||
pub struct ControlPanelModel {
|
||||
open_button: Controller<OpenButton>,
|
||||
@ -129,26 +131,39 @@ impl SimpleComponent for ControlPanelModel {
|
||||
}
|
||||
},
|
||||
gtk::Frame{
|
||||
set_width_request: 500,
|
||||
gtk::Box{
|
||||
set_orientation:gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_margin_horizontal:10,
|
||||
set_margin_vertical:10,
|
||||
set_spacing: 4,
|
||||
gtk::Label{
|
||||
set_label: "TimeLine",
|
||||
add_css_class:"h2",
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
gtk::Box{
|
||||
TimeLine{
|
||||
set_width_request: 500,
|
||||
set_height_request: 40,
|
||||
set_width_request: 381,
|
||||
set_time_start: Utc::now(),
|
||||
set_selection: Some(crate::timeline::Selection::Point(
|
||||
Utc::now() + Duration::hours(1)
|
||||
)),
|
||||
}
|
||||
},
|
||||
gtk::ScrolledWindow{
|
||||
set_height_request: 70,
|
||||
set_height_request: 75,
|
||||
#[local_ref]
|
||||
my_view -> gtk::ListView{
|
||||
add_css_class: "lv",
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
gtk::ScrolledWindow{
|
||||
set_vexpand: true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,11 +188,7 @@ impl SimpleComponent for ControlPanelModel {
|
||||
let mut list_img_wrapper: TypedListView<ImgItem, gtk::SingleSelection> =
|
||||
TypedListView::with_sorting();
|
||||
|
||||
list_img_wrapper.append(ImgItem::new(
|
||||
"00:00:00".to_string(),
|
||||
None,
|
||||
true,
|
||||
));
|
||||
list_img_wrapper.append(ImgItem::new("00:00:00".to_string(), None, true));
|
||||
|
||||
let model = ControlPanelModel {
|
||||
open_button,
|
||||
|
||||
@ -1,22 +1,51 @@
|
||||
use chrono::{prelude::*, Duration};
|
||||
use gtk::glib::clone;
|
||||
use gtk::prelude::{DrawingAreaExtManual, StyleContextExt};
|
||||
use gtk::prelude::{DrawingAreaExtManual, GestureSingleExt, StyleContextExt};
|
||||
use gtk::subclass::prelude::*;
|
||||
use gtk::traits::{GLAreaExt, WidgetExt};
|
||||
use gtk::{cairo, EventControllerMotion};
|
||||
use std::cell::RefCell;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::num::NonZeroU32;
|
||||
use std::rc::Rc;
|
||||
use svg::parser::Event;
|
||||
|
||||
use crate::render::Render;
|
||||
pub enum Selection {
|
||||
Slice((DateTime<Utc>, DateTime<Utc>)),
|
||||
Point(DateTime<Utc>),
|
||||
}
|
||||
|
||||
pub struct TimeLine {
|
||||
drawing_area: RefCell<Option<gtk::DrawingArea>>,
|
||||
height: Cell<u32>,
|
||||
width: Cell<u32>,
|
||||
pub(super) selection: Rc<RefCell<Option<Selection>>>,
|
||||
pub(super) margin_horizontal: Cell<u32>,
|
||||
pub(super) margin_vertical: Cell<u32>,
|
||||
pub(super) major_tick_interval: Cell<u32>,
|
||||
pub(super) major_tick_step: Cell<usize>,
|
||||
pub(super) minor_tick_step: Cell<usize>,
|
||||
pub(super) border_radius: Cell<f64>,
|
||||
pub(super) tick_selection: Rc<Cell<usize>>,
|
||||
pub(super) start_time: Cell<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Default for TimeLine {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
drawing_area: RefCell::new(None),
|
||||
height: Cell::new(40),
|
||||
width: Cell::new(380),
|
||||
selection: Rc::new(RefCell::new(None)),
|
||||
margin_horizontal: Cell::new(10),
|
||||
margin_vertical: Cell::new(0),
|
||||
major_tick_interval: Cell::new(30),
|
||||
major_tick_step: Cell::new(3600),
|
||||
minor_tick_step: Cell::new(360),
|
||||
border_radius: Cell::new(8.0),
|
||||
tick_selection: Rc::new(Cell::new(0)),
|
||||
start_time: Cell::new(Self::round_to_nearest(Utc::now(), 360)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,92 +68,139 @@ impl ObjectImpl for TimeLine {
|
||||
let prefers_dark_theme = settings.is_gtk_application_prefer_dark_theme();
|
||||
|
||||
let obj = self.obj().clone();
|
||||
|
||||
let (r, g, b) = if prefers_dark_theme {
|
||||
(1.0, 1.0, 1.0)
|
||||
let ((r, g, b), (br, bg, bb)) = if prefers_dark_theme {
|
||||
((1.0, 1.0, 1.0), (0.2274, 0.2274, 0.2274))
|
||||
} else {
|
||||
(0.0, 0.0, 0.0)
|
||||
((0.0, 0.0, 0.0), (0.89, 0.89, 0.89))
|
||||
};
|
||||
|
||||
let container = gtk::Box::builder()
|
||||
.orientation(gtk::Orientation::Horizontal)
|
||||
.height_request(70)
|
||||
.hexpand(true)
|
||||
.margin_start(10)
|
||||
.margin_end(10)
|
||||
.build();
|
||||
|
||||
let cursor_pos = std::rc::Rc::new(std::cell::RefCell::new(50.0));
|
||||
let cursor_pos = std::rc::Rc::new(std::cell::Cell::new(None));
|
||||
let cursor_pos_clone = cursor_pos.clone();
|
||||
|
||||
let cursor_pos_leave_clone = cursor_pos.clone();
|
||||
let motion_controller = EventControllerMotion::new();
|
||||
let cursor_pos_clone = cursor_pos.clone();
|
||||
let cursor_pos_clicker_clone = cursor_pos.clone();
|
||||
let height = self.height.get();
|
||||
let width = self.width.get();
|
||||
|
||||
let margin_horizontal = self.margin_horizontal.get() as f64;
|
||||
let major_tick_step = self.major_tick_step.get();
|
||||
let minor_tick_step = self.minor_tick_step.get();
|
||||
let major_tick_interval = self.major_tick_interval.get() as f64;
|
||||
let minor_tick_interval = major_tick_interval / (major_tick_step / minor_tick_step) as f64;
|
||||
let border_radius = self.border_radius.get();
|
||||
|
||||
drawing_area.set_height_request(height as i32);
|
||||
drawing_area.set_width_request(width as i32);
|
||||
|
||||
motion_controller.connect_motion(clone!(@weak drawing_area =>
|
||||
move |_, x, _| {
|
||||
*cursor_pos.borrow_mut() = x;
|
||||
drawing_area.queue_draw(); // 重绘时间线和游标
|
||||
cursor_pos.set(Some(x.clamp(margin_horizontal, width as f64 - margin_horizontal)));
|
||||
drawing_area.queue_draw();
|
||||
}));
|
||||
|
||||
motion_controller.connect_leave(clone!(@weak drawing_area => move |_| {
|
||||
cursor_pos_leave_clone.set(None);
|
||||
drawing_area.queue_draw();
|
||||
}));
|
||||
|
||||
let selection = self.selection.clone();
|
||||
let start = (&self.start_time).get();
|
||||
|
||||
drawing_area.set_draw_func(
|
||||
(move |_, cr, w, h| {
|
||||
cr.set_source_rgba(0.89, 0.89, 0.89, 1.0);
|
||||
cr.set_source_rgb(br, bg, bb);
|
||||
|
||||
Self::draw_rounded_rectangle(cr, 0.0, h as f64 / 2.0 - 20.0, w as f64, 45.0, 5.0);
|
||||
Self::draw_rounded_rectangle(cr, 0.0, 0.0, w as f64, h as f64, border_radius);
|
||||
let w = w - (2.0 * margin_horizontal) as i32;
|
||||
let w = w as f64;
|
||||
let h = h as f64;
|
||||
|
||||
let w = w - 20;
|
||||
cr.fill().unwrap();
|
||||
cr.set_source_rgb(r, g, b);
|
||||
cr.set_line_width(1.5);
|
||||
|
||||
let major_tick_interval = 30; // 每小时的像素宽度
|
||||
let minor_tick_interval = major_tick_interval / 10;
|
||||
|
||||
let y_pos = h as f64 / 2.0; // 时间轴位于中央
|
||||
cr.move_to(10.0, y_pos);
|
||||
cr.line_to(w as f64, y_pos);
|
||||
let y_pos = h / 2.0; // 时间轴位于中央
|
||||
cr.move_to(margin_horizontal, y_pos);
|
||||
cr.line_to(w + margin_horizontal, y_pos);
|
||||
cr.stroke().unwrap();
|
||||
|
||||
// 绘制大刻度(一小时)
|
||||
for i in 0..(w / major_tick_interval + 1) {
|
||||
let x_pos = i * major_tick_interval;
|
||||
cr.set_line_width(1.0);
|
||||
cr.move_to(x_pos as f64 + 10.0, h as f64 / 2.0);
|
||||
cr.line_to(x_pos as f64 + 10.0, h as f64 / 2.0 - 8.0);
|
||||
cr.stroke().unwrap();
|
||||
let time_text = format!("{}", i); // 假设时间轴从0点开始
|
||||
cr.move_to(x_pos as f64 + 10.0, y_pos + 15.0); // 文本位置
|
||||
cr.show_text(&time_text).unwrap();
|
||||
{
|
||||
let mut time_cursor = 0.0;
|
||||
let mut time_stamp = start.timestamp();
|
||||
let minor_tick_step = minor_tick_step as i64;
|
||||
let major_tick_step = major_tick_step as i64;
|
||||
while time_cursor < w {
|
||||
if time_stamp % major_tick_step == 0 {
|
||||
cr.set_line_width(1.0);
|
||||
cr.move_to(time_cursor + margin_horizontal, h / 2.0);
|
||||
cr.line_to(time_cursor + margin_horizontal, h / 2.0 - 8.0);
|
||||
cr.stroke().unwrap();
|
||||
time_cursor += minor_tick_interval;
|
||||
time_stamp += minor_tick_step;
|
||||
continue;
|
||||
}
|
||||
cr.set_line_width(0.5);
|
||||
cr.move_to(time_cursor + margin_horizontal, h / 2.0);
|
||||
cr.line_to(time_cursor + margin_horizontal, h / 2.0 - 5.0);
|
||||
cr.stroke().unwrap();
|
||||
time_cursor += minor_tick_interval;
|
||||
time_stamp += minor_tick_step;
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制小刻度(一分钟)
|
||||
for i in 0..(w / minor_tick_interval + 1) {
|
||||
let x_pos = i * minor_tick_interval;
|
||||
cr.set_line_width(0.5);
|
||||
cr.move_to(x_pos as f64 + 10.0, h as f64 / 2.0);
|
||||
cr.line_to(x_pos as f64 + 10.0, h as f64 / 2.0 - 5.0);
|
||||
cr.stroke().unwrap();
|
||||
cr.set_source_rgb(1.0, 1.0, 1.0);
|
||||
if let Some(x) = cursor_pos_clone.get() {
|
||||
Self::draw_cursor(cr, x, h / 2.0 - 15.0, 2f64, 35f64);
|
||||
}
|
||||
|
||||
cr.set_source_rgb(0.98, 0.26, 0.24); // 红色
|
||||
|
||||
Self::draw_cursor(
|
||||
cr,
|
||||
*cursor_pos_clone.borrow(),
|
||||
h as f64 / 2.0 - 15.0,
|
||||
2f64,
|
||||
35f64,
|
||||
);
|
||||
if let Some(selection) = selection.borrow().as_ref() {
|
||||
match selection {
|
||||
Selection::Point(p) => {
|
||||
let duration = p.signed_duration_since(start);
|
||||
let secs = duration.num_seconds();
|
||||
if secs > 0 {
|
||||
let x_pos =
|
||||
secs as f64 / major_tick_step as f64 * major_tick_interval;
|
||||
Self::draw_cursor(
|
||||
cr,
|
||||
x_pos + margin_horizontal,
|
||||
h / 2.0 - 15.0,
|
||||
2f64,
|
||||
35f64,
|
||||
);
|
||||
}
|
||||
}
|
||||
Selection::Slice((p1, p2)) => {}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
drawing_area.add_controller(motion_controller);
|
||||
let gesture_click = gtk::GestureClick::new();
|
||||
gesture_click.set_button(1);
|
||||
let mut gesture_click_selection = self.selection.clone();
|
||||
gesture_click.connect_pressed(clone!(@weak drawing_area =>move |gesture, _, x, y| {
|
||||
let x = x.clamp(margin_horizontal, width as f64 - margin_horizontal);
|
||||
let secs = (x - margin_horizontal) / (major_tick_interval / major_tick_step as f64);
|
||||
|
||||
drawing_area.set_parent(&container);
|
||||
gesture_click_selection.replace(Some(Selection::Point(
|
||||
start + Duration::seconds(secs as i64),
|
||||
)));
|
||||
|
||||
cursor_pos_clicker_clone.replace(None);
|
||||
drawing_area.queue_draw();
|
||||
|
||||
}));
|
||||
|
||||
drawing_area.add_controller(gesture_click);
|
||||
drawing_area.add_controller(motion_controller);
|
||||
drawing_area.set_parent(&obj);
|
||||
drawing_area.set_vexpand(true);
|
||||
drawing_area.set_hexpand(true);
|
||||
self.drawing_area.borrow_mut().replace(drawing_area);
|
||||
|
||||
container.set_parent(&obj);
|
||||
self.parent_constructed();
|
||||
}
|
||||
}
|
||||
@ -181,3 +257,26 @@ impl TimeLine {
|
||||
cr.fill().expect("Failed to fill the triangle");
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeLine {
|
||||
pub(super) fn set_start(&self, start: DateTime<Utc>) {
|
||||
let nearest = Self::round_to_nearest(start, self.minor_tick_step.get());
|
||||
self.start_time.set(nearest);
|
||||
}
|
||||
|
||||
fn round_to_nearest(dt: DateTime<Utc>, _seconds: usize) -> DateTime<Utc> {
|
||||
let _seconds = _seconds as i64;
|
||||
|
||||
let seconds = dt.timestamp();
|
||||
let rounded_seconds = (seconds / _seconds) * _seconds; // 向下规整到最近的整 6 分钟
|
||||
let remainder = seconds % _seconds;
|
||||
|
||||
let stamp = if remainder >= _seconds / 2 {
|
||||
seconds + (_seconds - remainder) as i64
|
||||
} else {
|
||||
seconds - remainder as i64
|
||||
};
|
||||
|
||||
Utc.timestamp_opt(stamp, 0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
mod imp;
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use glib::subclass::prelude::*;
|
||||
use glib::{clone, Time};
|
||||
use gtk::traits::WidgetExt;
|
||||
use gtk::{EventControllerScrollFlags, Inhibit};
|
||||
pub use imp::Selection;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TimeLine(ObjectSubclass<imp::TimeLine>)
|
||||
@ -20,4 +22,19 @@ impl TimeLine {
|
||||
let this: Self = glib::Object::new();
|
||||
this
|
||||
}
|
||||
|
||||
pub fn set_time_start(&self, start_time: DateTime<Utc>) {
|
||||
self.imp().set_start(start_time);
|
||||
}
|
||||
pub fn set_border_radius(&self) {}
|
||||
pub fn set_margin_horizontal(&self) {}
|
||||
pub fn set_margin_vertical(&self) {}
|
||||
|
||||
pub fn set_major_tick_interval(&self) {}
|
||||
pub fn set_major_tick_step(&self) {}
|
||||
pub fn set_minor_tick_step(&self) {}
|
||||
pub fn set_selection(&self, selection: Option<Selection>) {
|
||||
let self_ = self.imp();
|
||||
self_.selection.replace(selection);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user