first runnable
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
6344
Cargo.lock
generated
Normal file
6344
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "my-clock"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
slint = "1.15.1"
|
||||
chrono = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.15.1"
|
||||
225
src/main.rs
Normal file
225
src/main.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use chrono::{Local, Timelike};
|
||||
|
||||
slint::slint! {
|
||||
export component AppWindow inherits Window {
|
||||
title: "Modern Clock";
|
||||
no-frame: true;
|
||||
background: transparent;
|
||||
always-on-top: root.is_on_top;
|
||||
width: root.clock_size;
|
||||
height: root.clock_size;
|
||||
|
||||
in-out property <float> hour: 0.0;
|
||||
in-out property <float> minute: 0.0;
|
||||
in-out property <float> second: 0.0;
|
||||
in-out property <bool> is_on_top: true;
|
||||
in-out property <float> dial_opacity: 0.85;
|
||||
in-out property <length> clock_size: 300px;
|
||||
|
||||
property <length> mouse_start_x;
|
||||
property <length> mouse_start_y;
|
||||
|
||||
callback toggle_always_on_top();
|
||||
callback close_app();
|
||||
callback move_window(length, length);
|
||||
|
||||
menu_popup := PopupWindow {
|
||||
x: touch_area.mouse-x;
|
||||
y: touch_area.mouse-y;
|
||||
width: 160px;
|
||||
height: 200px;
|
||||
|
||||
Rectangle {
|
||||
background: #1e1e1e;
|
||||
border-radius: 10px;
|
||||
border-width: 1px;
|
||||
border-color: #444;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 15px;
|
||||
spacing: 10px;
|
||||
alignment: start;
|
||||
Text { text: "设置"; color: #888; font-size: 12px; horizontal-alignment: center; }
|
||||
Rectangle { height: 1px; background: #333; }
|
||||
|
||||
Rectangle {
|
||||
height: 30px;
|
||||
background: t1.has-hover ? #333 : transparent;
|
||||
border-radius: 4px;
|
||||
Text { text: root.is_on_top ? "取消置顶" : "总是置顶"; color: #ddd; font-size: 14px; }
|
||||
t1 := TouchArea { clicked => { root.toggle_always_on_top(); menu_popup.close(); } }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 30px;
|
||||
background: t2.has-hover ? #333 : transparent;
|
||||
border-radius: 4px;
|
||||
Text { text: "增大尺寸"; color: #ddd; font-size: 14px; }
|
||||
t2 := TouchArea { clicked => { root.clock_size += 50px; } }
|
||||
}
|
||||
Rectangle {
|
||||
height: 30px;
|
||||
background: t3.has-hover ? #333 : transparent;
|
||||
border-radius: 4px;
|
||||
Text { text: "减小尺寸"; color: #ddd; font-size: 14px; }
|
||||
t3 := TouchArea { clicked => { root.clock_size = Math.max(150px, root.clock_size - 50px); } }
|
||||
}
|
||||
|
||||
Rectangle { height: 1px; background: #333; }
|
||||
|
||||
Rectangle {
|
||||
height: 30px;
|
||||
background: t4.has-hover ? #552222 : transparent;
|
||||
border-radius: 4px;
|
||||
Text { text: "退出程序"; color: #ff6666; font-size: 14px; }
|
||||
t4 := TouchArea { clicked => { root.close_app(); } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touch_area := TouchArea {
|
||||
pointer-event(event) => {
|
||||
if (event.button == PointerEventButton.right && event.kind == PointerEventKind.down) {
|
||||
menu_popup.show();
|
||||
}
|
||||
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
|
||||
root.mouse_start_x = self.mouse-x;
|
||||
root.mouse_start_y = self.mouse-y;
|
||||
}
|
||||
}
|
||||
moved => {
|
||||
if (self.pressed) {
|
||||
root.move_window(self.mouse-x - root.mouse_start_x, self.mouse-y - root.mouse_start_y);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(25, 25, 25, root.dial_opacity);
|
||||
border-radius: self.width / 2;
|
||||
border-width: 1.5px;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
|
||||
for i in 12 : Path {
|
||||
viewbox-width: 100; viewbox-height: 100;
|
||||
width: 100%; height: 100%;
|
||||
stroke: rgba(255, 255, 255, 0.4);
|
||||
stroke-width: 2px;
|
||||
property <float> angle: i * 30;
|
||||
MoveTo {
|
||||
x: 50 + Math.sin(parent.angle * 1deg) * 42;
|
||||
y: 50 - Math.cos(parent.angle * 1deg) * 42;
|
||||
}
|
||||
LineTo {
|
||||
x: 50 + Math.sin(parent.angle * 1deg) * 47;
|
||||
y: 50 - Math.cos(parent.angle * 1deg) * 47;
|
||||
}
|
||||
}
|
||||
|
||||
for entry in [ {t: "12", a: 0}, {t: "3", a: 90}, {t: "6", a: 180}, {t: "9", a: 270} ] : Text {
|
||||
property <float> rad: entry.a * 3.14159 / 180;
|
||||
property <length> r: parent.width / 2 - 35px;
|
||||
x: parent.width / 2 + Math.sin(rad * 1rad) * r - self.width / 2;
|
||||
y: parent.height / 2 - Math.cos(rad * 1rad) * r - self.height / 2;
|
||||
text: entry.t;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
Path {
|
||||
viewbox-width: 100; viewbox-height: 100;
|
||||
width: 100%; height: 100%;
|
||||
stroke: #ffffff;
|
||||
stroke-width: 4px;
|
||||
MoveTo { x: 50; y: 50; }
|
||||
LineTo {
|
||||
x: 50 + Math.sin(root.hour * 1deg) * 25;
|
||||
y: 50 - Math.cos(root.hour * 1deg) * 25;
|
||||
}
|
||||
}
|
||||
|
||||
Path {
|
||||
viewbox-width: 100; viewbox-height: 100;
|
||||
width: 100%; height: 100%;
|
||||
stroke: #bbbbbb;
|
||||
stroke-width: 3px;
|
||||
MoveTo { x: 50; y: 50; }
|
||||
LineTo {
|
||||
x: 50 + Math.sin(root.minute * 1deg) * 35;
|
||||
y: 50 - Math.cos(root.minute * 1deg) * 35;
|
||||
}
|
||||
}
|
||||
|
||||
Path {
|
||||
viewbox-width: 100; viewbox-height: 100;
|
||||
width: 100%; height: 100%;
|
||||
stroke: #ff3b30;
|
||||
stroke-width: 1.5px;
|
||||
MoveTo { x: 50; y: 50; }
|
||||
LineTo {
|
||||
x: 50 + Math.sin(root.second * 1deg) * 42;
|
||||
y: 50 - Math.cos(root.second * 1deg) * 42;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: 8px; height: 8px;
|
||||
background: #222;
|
||||
border-radius: 4px;
|
||||
border-width: 2px;
|
||||
border-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), slint::PlatformError> {
|
||||
let ui = AppWindow::new()?;
|
||||
let ui_handle = ui.as_weak();
|
||||
|
||||
let timer = slint::Timer::default();
|
||||
timer.start(slint::TimerMode::Repeated, std::time::Duration::from_millis(1000), move || {
|
||||
if let Some(ui) = ui_handle.upgrade() {
|
||||
let now = Local::now();
|
||||
let h = (now.hour() % 12) as f32;
|
||||
let m = now.minute() as f32;
|
||||
let s = now.second() as f32;
|
||||
ui.set_second(s * 6.0);
|
||||
ui.set_minute(m * 6.0 + s * 0.1);
|
||||
ui.set_hour(h * 30.0 + m * 0.5);
|
||||
}
|
||||
});
|
||||
|
||||
let ui_exit = ui.as_weak();
|
||||
ui.on_close_app(move || { if let Some(ui) = ui_exit.upgrade() { let _ = ui.hide(); } });
|
||||
|
||||
let ui_top = ui.as_weak();
|
||||
ui.on_toggle_always_on_top(move || {
|
||||
if let Some(ui) = ui_top.upgrade() {
|
||||
let current = ui.get_is_on_top();
|
||||
ui.set_is_on_top(!current);
|
||||
}
|
||||
});
|
||||
|
||||
let ui_move = ui.as_weak();
|
||||
ui.on_move_window(move |ox, oy| {
|
||||
if let Some(ui) = ui_move.upgrade() {
|
||||
let pos = ui.window().position();
|
||||
ui.window().set_position(slint::WindowPosition::Logical(slint::LogicalPosition::new(
|
||||
pos.x as f32 + ox,
|
||||
pos.y as f32 + oy,
|
||||
)));
|
||||
}
|
||||
});
|
||||
|
||||
let now = Local::now();
|
||||
ui.set_second(now.second() as f32 * 6.0);
|
||||
ui.set_minute(now.minute() as f32 * 6.0);
|
||||
ui.set_hour((now.hour() % 12) as f32 * 30.0 + now.minute() as f32 * 0.5);
|
||||
|
||||
ui.run()
|
||||
}
|
||||
Reference in New Issue
Block a user