use crate::clock::Clock;
use crate::config::Config;
use crate::drag::DragController;
use crate::platform;
use crate::render::{parse_alignment, parse_font_weight, parse_hex, ClockRenderer, Style};
use crate::state::{load_window_state, save_window_state, WindowState};
use crate::window::LayerShellWindow;
use glib::clone;
use gtk::prelude::*;
use gtk::{CssProvider, DrawingArea, StyleContext};
use log::warn;
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
#[allow(clippy::needless_pass_by_value, clippy::too_many_lines)]
pub fn run(app: >k::Application, config: Config) {
let saved_state = load_window_state();
let start_x = config.pos_x.unwrap_or(saved_state.x);
let start_y = config.pos_y.unwrap_or(saved_state.y);
let initial_pos = (start_x, start_y);
let window_mover = Rc::new(LayerShellWindow::new(app, initial_pos, &config.layer));
let use_absolute = match config.backend.to_lowercase().as_str() {
"hyprland" => true,
"generic" => false,
_ => platform::is_hyprland(),
};
let drag_controller = Rc::new(RefCell::new(DragController::new(
initial_pos,
use_absolute,
)));
let style = Rc::new(Style {
font_family: config.font_family.clone(),
font_weight: parse_font_weight(&config.font_weight),
size_pt: config.size,
text_rgba: parse_hex(&config.color),
border_rgba: parse_hex(&config.border_color),
thickness: config.thickness,
alignment: parse_alignment(&config.alignment),
});
let renderer = Rc::new(ClockRenderer::new());
let clock = Rc::new(Clock::new(config.demo));
let time_text = Rc::new(RefCell::new(String::new()));
let drawing_area = DrawingArea::new();
window_mover.window().add(&drawing_area);
window_mover.window().add_events(
gtk::gdk::EventMask::BUTTON_PRESS_MASK
| gtk::gdk::EventMask::BUTTON_RELEASE_MASK
| gtk::gdk::EventMask::BUTTON1_MOTION_MASK,
);
window_mover.window().connect_button_press_event(clone!(
@strong window_mover,
@strong drag_controller => move |_, event| {
if event.button() == 1 {
let (x, y) = event.root();
#[allow(clippy::cast_possible_truncation)]
drag_controller.borrow_mut().press((x as i32, y as i32));
gtk::glib::Propagation::Stop
} else if event.button() == 3 {
window_mover.close();
gtk::glib::Propagation::Stop
} else {
gtk::glib::Propagation::Proceed
}
}
));
let save_pos = config.save_position;
window_mover.window().connect_button_release_event(clone!(
@strong drag_controller => move |_, event| {
if event.button() == 1 {
let final_margin = drag_controller.borrow_mut().release();
if save_pos {
save_window_state(WindowState {
x: final_margin.0,
y: final_margin.1,
});
}
gtk::glib::Propagation::Stop
} else {
gtk::glib::Propagation::Proceed
}
}
));
window_mover.window().connect_motion_notify_event(clone!(
@strong window_mover,
@strong drag_controller => move |_, event| {
let (x, y) = event.root();
#[allow(clippy::cast_possible_truncation)]
if let Some(new_margin) = drag_controller.borrow_mut().motion((x as i32, y as i32)) {
window_mover.set_margin(new_margin);
gtk::glib::Propagation::Stop
} else {
gtk::glib::Propagation::Proceed
}
}
));
drawing_area.connect_draw(clone!(
@strong time_text,
@strong renderer,
@strong style => move |_, cr| {
let text = time_text.borrow();
renderer.draw(cr, &text, &style);
gtk::glib::Propagation::Proceed
}
));
let handle_signal = clone!(
@strong window_mover,
@strong drag_controller => move || {
let margin = drag_controller.borrow().current_margin();
if save_pos {
save_window_state(WindowState {
x: margin.0,
y: margin.1,
});
}
window_mover.close();
glib::ControlFlow::Break
}
);
glib::unix_signal_add_local(2, handle_signal.clone());
glib::unix_signal_add_local(15, handle_signal);
let area_clone = drawing_area.clone();
let format = config.format.clone();
update_clock(&area_clone, &time_text, &clock, &format, &style);
if !config.demo {
schedule_timer_tick(
area_clone,
time_text,
clock,
format,
style,
);
}
apply_css();
window_mover.window().show_all();
}
#[allow(clippy::needless_pass_by_value)]
fn schedule_timer_tick(
area: DrawingArea,
time_text: Rc<RefCell<String>>,
clock: Rc<Clock>,
format: String,
style: Rc<Style>,
) {
let delay_ms = clock.millis_until_next_tick(&format);
glib::timeout_add_local(
Duration::from_millis(delay_ms),
clone!(
@strong area,
@strong time_text,
@strong clock,
@strong format,
@strong style => move || {
update_clock(&area, &time_text, &clock, &format, &style);
schedule_timer_tick(
area.clone(),
time_text.clone(),
clock.clone(),
format.clone(),
style.clone(),
);
glib::ControlFlow::Break
}
),
);
}
fn update_clock(
area: &DrawingArea,
text_ref: &Rc<RefCell<String>>,
clock: &Clock,
format_str: &str,
style: &Style,
) {
let time_str = clock.now_formatted(format_str);
if *text_ref.borrow() != time_str {
text_ref.borrow_mut().clone_from(&time_str);
let layout = area.create_pango_layout(Some(&time_str));
let mut font_desc = pango::FontDescription::new();
font_desc.set_family(&style.font_family);
#[allow(clippy::cast_possible_wrap)]
font_desc.set_size(style.size_pt as i32 * pango::SCALE);
font_desc.set_weight(style.font_weight);
layout.set_font_description(Some(&font_desc));
layout.set_alignment(style.alignment);
let (width, height) = layout.pixel_size();
let padding = style.thickness * 2;
area.set_size_request(width + padding, height + padding);
area.queue_draw();
}
}
fn apply_css() {
let css = if let Ok(content) = std::fs::read_to_string("assets/style.css") {
content
} else {
"window { background-color: rgba(0, 0, 0, 0.01); }".to_string()
};
let provider = CssProvider::new();
if let Err(e) = provider.load_from_data(css.as_bytes()) {
warn!("Failed to load CSS: {e}");
}
if let Some(screen) = gtk::gdk::Screen::default() {
StyleContext::add_provider_for_screen(
&screen,
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
}
}