Compare commits
4 Commits
d9a10c76b0
...
bed75dc669
Author | SHA1 | Date | |
---|---|---|---|
bed75dc669 | |||
e7bf84a744 | |||
40e2fbf658 | |||
5e57767921 |
@ -1,15 +1,23 @@
|
||||
use cairo::{Context, FontFace, Format, ImageSurface, LineCap, Pattern};
|
||||
use cairo::{
|
||||
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
|
||||
TextExtents,
|
||||
};
|
||||
use glib::Object;
|
||||
use gtk::{prelude::*, subclass::prelude::*};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
const WIDTH: i32 = 800;
|
||||
const HEIGHT: i32 = 800;
|
||||
const WIDTH: i32 = 1920;
|
||||
const HEIGHT: i32 = 1280;
|
||||
|
||||
pub struct SplashPrivate {
|
||||
text: Rc<RefCell<String>>,
|
||||
time: Rc<RefCell<String>>,
|
||||
time: Rc<RefCell<Duration>>,
|
||||
background: Rc<RefCell<Pattern>>,
|
||||
time_extents: Rc<RefCell<Option<TextExtents>>>,
|
||||
}
|
||||
|
||||
impl SplashPrivate {
|
||||
@ -17,7 +25,7 @@ impl SplashPrivate {
|
||||
*self.text.borrow_mut() = text;
|
||||
}
|
||||
|
||||
fn set_time(&self, time: String) {
|
||||
fn set_time(&self, time: Duration) {
|
||||
*self.time.borrow_mut() = time;
|
||||
}
|
||||
|
||||
@ -28,24 +36,83 @@ impl SplashPrivate {
|
||||
context.set_source_rgb(0., 0., 0.);
|
||||
let _ = context.paint();
|
||||
|
||||
context.set_font_size(64.);
|
||||
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
|
||||
context.set_font_size(128.);
|
||||
|
||||
let center_x = WIDTH as f64 / 2.;
|
||||
let center_y = HEIGHT as f64 / 2.;
|
||||
|
||||
let title_extents = context.text_extents(&self.text.borrow()).unwrap();
|
||||
let title_width = title_extents.width();
|
||||
let title_height = title_extents.height();
|
||||
|
||||
context.set_source_rgb(0.7, 0., 1.);
|
||||
context.set_line_width(5.);
|
||||
context.set_line_cap(LineCap::Round);
|
||||
AsymLineReturn {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: 20.,
|
||||
start_y: 20.,
|
||||
start_length: 200.,
|
||||
center_length: title_extents.y_advance(),
|
||||
end_length: 100.,
|
||||
width: title_extents.height(),
|
||||
{
|
||||
let start_length = center_x - title_width / 2. - title_height - 20.;
|
||||
|
||||
let title_cutout = AsymLineCutout {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: 20.,
|
||||
start_y: center_y - 20. - title_height / 2.,
|
||||
start_length,
|
||||
total_length: WIDTH as f64 - 120.,
|
||||
cutout_length: title_width,
|
||||
height: title_height,
|
||||
invert: false,
|
||||
};
|
||||
|
||||
context.set_line_cap(LineCap::Round);
|
||||
context.set_source_rgb(0.7, 0., 1.);
|
||||
context.set_line_width(2.);
|
||||
title_cutout.draw(&context);
|
||||
let _ = context.stroke();
|
||||
}
|
||||
|
||||
{
|
||||
let title_baseline_x = center_x - title_width / 2.;
|
||||
let title_baseline_y = center_y - 20.;
|
||||
|
||||
let gradient = LinearGradient::new(
|
||||
title_baseline_x,
|
||||
title_baseline_y - title_height,
|
||||
title_baseline_x,
|
||||
title_baseline_y,
|
||||
);
|
||||
gradient.add_color_stop_rgb(0.2, 0.7, 0.0, 1.0);
|
||||
gradient.add_color_stop_rgb(0.8, 0.2, 0.0, 1.0);
|
||||
context.move_to(title_baseline_x, title_baseline_y);
|
||||
let _ = context.set_source(gradient);
|
||||
let _ = context.show_text(&self.text.borrow());
|
||||
}
|
||||
|
||||
{
|
||||
context.set_source_rgb(0.7, 0., 1.);
|
||||
AsymLine {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: 100.,
|
||||
start_y: HEIGHT as f64 / 2. + 100.,
|
||||
start_length: 400.,
|
||||
height: 50.,
|
||||
total_length: 650.,
|
||||
invert: true,
|
||||
}
|
||||
.draw(&context);
|
||||
let _ = context.stroke();
|
||||
}
|
||||
|
||||
{
|
||||
context.set_source_rgb(0.7, 0., 1.);
|
||||
AsymLine {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: WIDTH as f64 / 2. + 100.,
|
||||
start_y: HEIGHT as f64 / 2. + 200.,
|
||||
start_length: 600.,
|
||||
height: 50.,
|
||||
total_length: 650.,
|
||||
invert: false,
|
||||
}
|
||||
.draw(&context);
|
||||
let _ = context.stroke();
|
||||
}
|
||||
.draw(&context);
|
||||
let _ = context.stroke();
|
||||
|
||||
let background = context.pop_group().unwrap();
|
||||
|
||||
@ -70,8 +137,9 @@ impl ObjectSubclass for SplashPrivate {
|
||||
|
||||
SplashPrivate {
|
||||
text: Rc::new(RefCell::new(String::from(""))),
|
||||
time: Rc::new(RefCell::new(String::from(""))),
|
||||
time: Rc::new(RefCell::new(Duration::ZERO)),
|
||||
background: Rc::new(RefCell::new(background)),
|
||||
time_extents: Rc::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,7 +152,7 @@ glib::wrapper! {
|
||||
}
|
||||
|
||||
impl Splash {
|
||||
pub fn new(text: String, time: String) -> Self {
|
||||
pub fn new(text: String, time: Duration) -> Self {
|
||||
let s: Self = Object::builder().build();
|
||||
s.set_width_request(WIDTH);
|
||||
s.set_height_request(HEIGHT);
|
||||
@ -95,103 +163,249 @@ impl Splash {
|
||||
|
||||
s.set_draw_func({
|
||||
let s = s.clone();
|
||||
move |_, context, _width, _height| {
|
||||
move |_, context, width, height| {
|
||||
let background = s.imp().background.borrow();
|
||||
let _ = context.set_source(&*background);
|
||||
let _ = context.paint();
|
||||
|
||||
let time = s.imp().time.borrow().clone();
|
||||
let minutes = time.as_secs() / 60;
|
||||
let seconds = time.as_secs() % 60;
|
||||
|
||||
let center_x = width as f64 / 2.;
|
||||
let center_y = height as f64 / 2.;
|
||||
|
||||
{
|
||||
context.select_font_face(
|
||||
"Alegreya Sans SC",
|
||||
FontSlant::Normal,
|
||||
FontWeight::Bold,
|
||||
);
|
||||
context.set_font_size(128.);
|
||||
|
||||
let time = format!("{:02}' {:02}\"", minutes, seconds);
|
||||
|
||||
let time_extents = context.text_extents(&time).unwrap();
|
||||
|
||||
let mut saved_extents = s.imp().time_extents.borrow_mut();
|
||||
if saved_extents.is_none() {
|
||||
*saved_extents = Some(time_extents.clone());
|
||||
}
|
||||
|
||||
let time_baseline_x = center_x - time_extents.width() / 2.;
|
||||
let time_baseline_y = center_y + 100.;
|
||||
|
||||
let gradient = LinearGradient::new(
|
||||
time_baseline_x,
|
||||
time_baseline_y - time_extents.height(),
|
||||
time_baseline_x,
|
||||
time_baseline_y,
|
||||
);
|
||||
gradient.add_color_stop_rgb(0.2, 0.2, 0.0, 1.0);
|
||||
gradient.add_color_stop_rgb(0.8, 0.7, 0.0, 1.0);
|
||||
context.move_to(time_baseline_x, time_baseline_y);
|
||||
let _ = context.set_source(gradient);
|
||||
let _ = context.show_text(&time);
|
||||
};
|
||||
|
||||
match *s.imp().time_extents.borrow() {
|
||||
Some(extents) => {
|
||||
context.set_source_rgb(0.7, 0.0, 1.0);
|
||||
let time_meter = SlashMeter {
|
||||
orientation: gtk::Orientation::Horizontal,
|
||||
start_x: center_x + extents.width() / 2. + 50.,
|
||||
start_y: center_y + 100.,
|
||||
count: 5,
|
||||
fill_count: minutes as u8,
|
||||
height: 60.,
|
||||
length: 100.,
|
||||
};
|
||||
time_meter.draw(&context);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn set_time(&self, time: Duration) {
|
||||
self.imp().set_time(time);
|
||||
self.queue_draw();
|
||||
}
|
||||
}
|
||||
|
||||
struct AsymLineReturn {
|
||||
struct AsymLineCutout {
|
||||
orientation: gtk::Orientation,
|
||||
start_x: f64,
|
||||
start_y: f64,
|
||||
start_length: f64,
|
||||
center_length: f64,
|
||||
end_length: f64,
|
||||
width: f64,
|
||||
total_length: f64,
|
||||
cutout_length: f64,
|
||||
height: f64,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl AsymLineReturn {
|
||||
impl AsymLineCutout {
|
||||
fn draw(&self, context: &Context) {
|
||||
let dodge = if self.invert {
|
||||
self.height
|
||||
} else {
|
||||
-self.height
|
||||
};
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
context.move_to(self.start_x, self.start_y);
|
||||
context.line_to(self.start_x + self.start_length, self.start_y);
|
||||
context.line_to(
|
||||
self.start_x + self.start_length + self.width.abs(),
|
||||
self.start_y + self.width,
|
||||
self.start_x + self.start_length + self.height,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
context.line_to(
|
||||
self.start_x + self.start_length + self.width.abs() + self.center_length,
|
||||
self.start_y + self.width,
|
||||
self.start_x + self.start_length + self.height + self.cutout_length,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
context.line_to(
|
||||
self.start_x
|
||||
+ self.start_length
|
||||
+ self.width.abs()
|
||||
+ self.center_length
|
||||
+ (self.width.abs() / 2.),
|
||||
self.start_y + self.width / 2.,
|
||||
);
|
||||
context.line_to(
|
||||
self.start_x
|
||||
+ self.start_length
|
||||
+ self.width.abs()
|
||||
+ self.center_length
|
||||
+ (self.width.abs() / 2.)
|
||||
+ self.end_length,
|
||||
self.start_y + self.width / 2.,
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.),
|
||||
self.start_y + dodge / 2.,
|
||||
);
|
||||
context.line_to(self.total_length, self.start_y + dodge / 2.);
|
||||
}
|
||||
gtk::Orientation::Vertical => {
|
||||
context.move_to(self.start_x, self.start_y);
|
||||
context.line_to(self.start_x, self.start_y + self.start_length);
|
||||
context.line_to(
|
||||
self.start_x + self.width,
|
||||
self.start_y + self.start_length + self.width.abs(),
|
||||
self.start_x + dodge,
|
||||
self.start_y + self.start_length + self.height,
|
||||
);
|
||||
context.line_to(
|
||||
self.start_x + self.width,
|
||||
self.start_y + self.start_length + self.width.abs() + self.center_length,
|
||||
self.start_x + dodge,
|
||||
self.start_y + self.start_length + self.height + self.cutout_length,
|
||||
);
|
||||
context.line_to(
|
||||
self.start_x + self.width / 2.,
|
||||
self.start_x + dodge / 2.,
|
||||
self.start_y
|
||||
+ self.start_length
|
||||
+ self.width.abs()
|
||||
+ self.center_length
|
||||
+ (self.width.abs() / 2.),
|
||||
);
|
||||
context.line_to(
|
||||
self.start_x + self.width / 2.,
|
||||
self.start_y
|
||||
+ self.start_length
|
||||
+ self.width.abs()
|
||||
+ self.center_length
|
||||
+ (self.width.abs() / 2.)
|
||||
+ self.end_length,
|
||||
+ self.height
|
||||
+ self.cutout_length
|
||||
+ (self.height / 2.),
|
||||
);
|
||||
context.line_to(self.start_x + dodge / 2., self.total_length);
|
||||
}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AsymLine {
|
||||
orientation: gtk::Orientation,
|
||||
start_x: f64,
|
||||
start_y: f64,
|
||||
start_length: f64,
|
||||
height: f64,
|
||||
total_length: f64,
|
||||
invert: bool,
|
||||
}
|
||||
|
||||
impl AsymLine {
|
||||
fn draw(&self, context: &Context) {
|
||||
let dodge = if self.invert {
|
||||
self.height
|
||||
} else {
|
||||
-self.height
|
||||
};
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
context.move_to(self.start_x, self.start_y);
|
||||
context.line_to(self.start_x + self.start_length, self.start_y);
|
||||
context.line_to(
|
||||
self.start_x + self.start_length + self.height,
|
||||
self.start_y + dodge,
|
||||
);
|
||||
context.line_to(self.start_x + self.total_length, self.start_y + dodge);
|
||||
}
|
||||
gtk::Orientation::Vertical => {}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SlashMeter {
|
||||
orientation: gtk::Orientation,
|
||||
start_x: f64,
|
||||
start_y: f64,
|
||||
count: u8,
|
||||
fill_count: u8,
|
||||
height: f64,
|
||||
length: f64,
|
||||
}
|
||||
|
||||
impl SlashMeter {
|
||||
fn draw(&self, context: &Context) {
|
||||
match self.orientation {
|
||||
gtk::Orientation::Horizontal => {
|
||||
let angle: f64 = 0.8;
|
||||
let run = self.height / angle.tan();
|
||||
let width = self.length as f64 / (self.count as f64 * 2.);
|
||||
|
||||
for c in 0..self.count {
|
||||
context.set_line_width(1.);
|
||||
|
||||
let start_x = self.start_x + c as f64 * width * 2.;
|
||||
context.move_to(start_x, self.start_y);
|
||||
context.line_to(start_x + run, self.start_y - self.height);
|
||||
context.line_to(start_x + run + width, self.start_y - self.height);
|
||||
context.line_to(start_x + width, self.start_y);
|
||||
context.line_to(start_x, self.start_y);
|
||||
if c < self.fill_count {
|
||||
let _ = context.fill();
|
||||
} else {
|
||||
let _ = context.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
gtk::Orientation::Vertical => {}
|
||||
_ => panic!("unknown orientation"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = gtk::Application::builder()
|
||||
.application_id("com.luminescent-dreams.cyberpunk-splash")
|
||||
.build();
|
||||
|
||||
app.connect_activate(move |app| {
|
||||
let (gtk_tx, gtk_rx) =
|
||||
gtk::glib::MainContext::channel::<Duration>(gtk::glib::PRIORITY_DEFAULT);
|
||||
let window = gtk::ApplicationWindow::new(app);
|
||||
window.present();
|
||||
|
||||
window.set_child(Some(&Splash::new("Kifu".to_owned(), "4:23".to_owned())));
|
||||
let mut countdown = Duration::from_secs(5 * 60);
|
||||
let mut next_tick = Instant::now() + Duration::from_secs(1);
|
||||
|
||||
let splash = Splash::new("GTK Kifu".to_owned(), countdown.clone());
|
||||
|
||||
window.set_child(Some(&splash));
|
||||
|
||||
gtk_rx.attach(None, move |time| {
|
||||
splash.set_time(time);
|
||||
Continue(true)
|
||||
});
|
||||
|
||||
std::thread::spawn(move || loop {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
if Instant::now() >= next_tick {
|
||||
countdown = countdown - Duration::from_secs(1);
|
||||
let _ = gtk_tx.send(countdown);
|
||||
next_tick = next_tick + Duration::from_secs(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.run();
|
||||
|
Loading…
Reference in New Issue
Block a user