Create a cyberpunk-style splash screen with a title and a countdown #39
|
@ -10,14 +10,49 @@ use std::{
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WIDTH: i32 = 1920;
|
const WIDTH: i32 = 1600;
|
||||||
const HEIGHT: i32 = 1280;
|
const HEIGHT: i32 = 600;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
enum Event {
|
||||||
|
Frames(u8),
|
||||||
|
Time(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimeoutAnimation {
|
||||||
|
intensity: f64,
|
||||||
|
duration: f64,
|
||||||
|
ascending: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeoutAnimation {
|
||||||
|
fn tick(&mut self, frames_elapsed: u8) {
|
||||||
|
let step_size = 1. / (self.duration * 60.);
|
||||||
|
if self.ascending {
|
||||||
|
self.intensity = self.intensity + step_size * frames_elapsed as f64;
|
||||||
|
if self.intensity > 1. {
|
||||||
|
self.intensity = 1.0;
|
||||||
|
self.ascending = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.intensity = self.intensity - step_size * frames_elapsed as f64;
|
||||||
|
if self.intensity < 0. {
|
||||||
|
self.intensity = 0.0;
|
||||||
|
self.ascending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SplashPrivate {
|
pub struct SplashPrivate {
|
||||||
text: Rc<RefCell<String>>,
|
text: Rc<RefCell<String>>,
|
||||||
time: Rc<RefCell<Duration>>,
|
time: Rc<RefCell<Duration>>,
|
||||||
background: Rc<RefCell<Pattern>>,
|
background: Rc<RefCell<Pattern>>,
|
||||||
time_extents: Rc<RefCell<Option<TextExtents>>>,
|
time_extents: Rc<RefCell<Option<TextExtents>>>,
|
||||||
|
width: Rc<RefCell<i32>>,
|
||||||
|
height: Rc<RefCell<i32>>,
|
||||||
|
|
||||||
|
timeout_animation: Rc<RefCell<Option<TimeoutAnimation>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SplashPrivate {
|
impl SplashPrivate {
|
||||||
|
@ -30,7 +65,9 @@ impl SplashPrivate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redraw_background(&self) {
|
fn redraw_background(&self) {
|
||||||
let background = ImageSurface::create(Format::Rgb24, WIDTH, HEIGHT).unwrap();
|
let background =
|
||||||
|
ImageSurface::create(Format::Rgb24, *self.width.borrow(), *self.height.borrow())
|
||||||
|
.unwrap();
|
||||||
let context = Context::new(background).unwrap();
|
let context = Context::new(background).unwrap();
|
||||||
context.push_group();
|
context.push_group();
|
||||||
context.set_source_rgb(0., 0., 0.);
|
context.set_source_rgb(0., 0., 0.);
|
||||||
|
@ -39,8 +76,8 @@ impl SplashPrivate {
|
||||||
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
|
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
|
||||||
context.set_font_size(128.);
|
context.set_font_size(128.);
|
||||||
|
|
||||||
let center_x = WIDTH as f64 / 2.;
|
let center_x = *self.width.borrow() as f64 / 2.;
|
||||||
let center_y = HEIGHT as f64 / 2.;
|
let center_y = *self.height.borrow() as f64 / 2.;
|
||||||
|
|
||||||
let title_extents = context.text_extents(&self.text.borrow()).unwrap();
|
let title_extents = context.text_extents(&self.text.borrow()).unwrap();
|
||||||
let title_width = title_extents.width();
|
let title_width = title_extents.width();
|
||||||
|
@ -54,7 +91,7 @@ impl SplashPrivate {
|
||||||
start_x: 20.,
|
start_x: 20.,
|
||||||
start_y: center_y - 20. - title_height / 2.,
|
start_y: center_y - 20. - title_height / 2.,
|
||||||
start_length,
|
start_length,
|
||||||
total_length: WIDTH as f64 - 120.,
|
total_length: *self.width.borrow() as f64 - 120.,
|
||||||
cutout_length: title_width,
|
cutout_length: title_width,
|
||||||
height: title_height,
|
height: title_height,
|
||||||
invert: false,
|
invert: false,
|
||||||
|
@ -89,7 +126,7 @@ impl SplashPrivate {
|
||||||
AsymLine {
|
AsymLine {
|
||||||
orientation: gtk::Orientation::Horizontal,
|
orientation: gtk::Orientation::Horizontal,
|
||||||
start_x: 100.,
|
start_x: 100.,
|
||||||
start_y: HEIGHT as f64 / 2. + 100.,
|
start_y: *self.height.borrow() as f64 / 2. + 100.,
|
||||||
start_length: 400.,
|
start_length: 400.,
|
||||||
height: 50.,
|
height: 50.,
|
||||||
total_length: 650.,
|
total_length: 650.,
|
||||||
|
@ -103,8 +140,8 @@ impl SplashPrivate {
|
||||||
context.set_source_rgb(0.7, 0., 1.);
|
context.set_source_rgb(0.7, 0., 1.);
|
||||||
AsymLine {
|
AsymLine {
|
||||||
orientation: gtk::Orientation::Horizontal,
|
orientation: gtk::Orientation::Horizontal,
|
||||||
start_x: WIDTH as f64 / 2. + 100.,
|
start_x: *self.width.borrow() as f64 / 2. + 100.,
|
||||||
start_y: HEIGHT as f64 / 2. + 200.,
|
start_y: *self.height.borrow() as f64 / 2. + 200.,
|
||||||
start_length: 600.,
|
start_length: 600.,
|
||||||
height: 50.,
|
height: 50.,
|
||||||
total_length: 650.,
|
total_length: 650.,
|
||||||
|
@ -140,6 +177,9 @@ impl ObjectSubclass for SplashPrivate {
|
||||||
time: Rc::new(RefCell::new(Duration::ZERO)),
|
time: Rc::new(RefCell::new(Duration::ZERO)),
|
||||||
background: Rc::new(RefCell::new(background)),
|
background: Rc::new(RefCell::new(background)),
|
||||||
time_extents: Rc::new(RefCell::new(None)),
|
time_extents: Rc::new(RefCell::new(None)),
|
||||||
|
width: Rc::new(RefCell::new(WIDTH)),
|
||||||
|
height: Rc::new(RefCell::new(HEIGHT)),
|
||||||
|
timeout_animation: Rc::new(RefCell::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,6 +215,8 @@ impl Splash {
|
||||||
let center_x = width as f64 / 2.;
|
let center_x = width as f64 / 2.;
|
||||||
let center_y = height as f64 / 2.;
|
let center_y = height as f64 / 2.;
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
{
|
{
|
||||||
context.select_font_face(
|
context.select_font_face(
|
||||||
"Alegreya Sans SC",
|
"Alegreya Sans SC",
|
||||||
|
@ -195,16 +237,42 @@ impl Splash {
|
||||||
let time_baseline_x = center_x - time_extents.width() / 2.;
|
let time_baseline_x = center_x - time_extents.width() / 2.;
|
||||||
let time_baseline_y = center_y + 100.;
|
let time_baseline_y = center_y + 100.;
|
||||||
|
|
||||||
|
/*
|
||||||
|
match *s.imp().timeout_animation.borrow() {
|
||||||
|
Some(ref animation) => {
|
||||||
|
context.set_source_rgb(animation.intensity / 2., 0., 0.);
|
||||||
|
RoundedRectangle {
|
||||||
|
x: time_baseline_x - 5.,
|
||||||
|
y: time_baseline_y + 10.,
|
||||||
|
width: time_extents.width() + 15.,
|
||||||
|
height: time_extents.height() + 15.,
|
||||||
|
}
|
||||||
|
.draw(&context);
|
||||||
|
// let _ = context.fill();
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
let gradient = LinearGradient::new(
|
let gradient = LinearGradient::new(
|
||||||
time_baseline_x,
|
time_baseline_x,
|
||||||
time_baseline_y - time_extents.height(),
|
time_baseline_y - time_extents.height(),
|
||||||
time_baseline_x,
|
time_baseline_x,
|
||||||
time_baseline_y,
|
time_baseline_y,
|
||||||
);
|
);
|
||||||
gradient.add_color_stop_rgb(0.2, 0.2, 0.0, 1.0);
|
match *s.imp().timeout_animation.borrow() {
|
||||||
gradient.add_color_stop_rgb(0.8, 0.7, 0.0, 1.0);
|
Some(ref animation) => {
|
||||||
|
gradient.add_color_stop_rgba(0.2, 0.2, 0.0, 1.0, animation.intensity);
|
||||||
|
gradient.add_color_stop_rgba(0.8, 0.7, 0.0, 1.0, animation.intensity);
|
||||||
|
let _ = context.set_source(gradient);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
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);
|
||||||
|
let _ = context.set_source(gradient);
|
||||||
|
}
|
||||||
|
}
|
||||||
context.move_to(time_baseline_x, time_baseline_y);
|
context.move_to(time_baseline_x, time_baseline_y);
|
||||||
let _ = context.set_source(gradient);
|
|
||||||
let _ = context.show_text(&time);
|
let _ = context.show_text(&time);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -227,13 +295,37 @@ impl Splash {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
s.connect_resize(|s, width, height| {
|
||||||
|
*s.imp().width.borrow_mut() = width;
|
||||||
|
*s.imp().height.borrow_mut() = height;
|
||||||
|
s.imp().redraw_background();
|
||||||
|
});
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_time(&self, time: Duration) {
|
pub fn set_time(&self, time: Duration) {
|
||||||
self.imp().set_time(time);
|
self.imp().set_time(time);
|
||||||
|
if time == Duration::ZERO {
|
||||||
|
*self.imp().timeout_animation.borrow_mut() = Some(TimeoutAnimation {
|
||||||
|
intensity: 1.,
|
||||||
|
duration: 1.,
|
||||||
|
ascending: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tick(&self, frames_elapsed: u8) {
|
||||||
|
let mut animation = self.imp().timeout_animation.borrow_mut();
|
||||||
|
match *animation {
|
||||||
|
Some(ref mut animation) => {
|
||||||
|
animation.tick(frames_elapsed);
|
||||||
|
self.queue_draw();
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AsymLineCutout {
|
struct AsymLineCutout {
|
||||||
|
@ -335,6 +427,36 @@ impl AsymLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RoundedRectangle {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoundedRectangle {
|
||||||
|
fn draw(&self, context: &Context) {
|
||||||
|
context.arc(
|
||||||
|
self.x,
|
||||||
|
self.y - self.height / 2.,
|
||||||
|
self.height / 2.,
|
||||||
|
0.5 * std::f64::consts::PI,
|
||||||
|
1.5 * std::f64::consts::PI,
|
||||||
|
);
|
||||||
|
let _ = context.fill();
|
||||||
|
context.arc(
|
||||||
|
self.x + self.width,
|
||||||
|
self.y - self.height / 2.,
|
||||||
|
self.height / 2.,
|
||||||
|
1.5 * std::f64::consts::PI,
|
||||||
|
0.5 * std::f64::consts::PI,
|
||||||
|
);
|
||||||
|
let _ = context.fill();
|
||||||
|
context.rectangle(self.x, self.y, self.width, -self.height);
|
||||||
|
let _ = context.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct SlashMeter {
|
struct SlashMeter {
|
||||||
orientation: gtk::Orientation,
|
orientation: gtk::Orientation,
|
||||||
start_x: f64,
|
start_x: f64,
|
||||||
|
@ -382,27 +504,36 @@ fn main() {
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
let (gtk_tx, gtk_rx) =
|
let (gtk_tx, gtk_rx) =
|
||||||
gtk::glib::MainContext::channel::<Duration>(gtk::glib::PRIORITY_DEFAULT);
|
gtk::glib::MainContext::channel::<Event>(gtk::glib::PRIORITY_DEFAULT);
|
||||||
let window = gtk::ApplicationWindow::new(app);
|
let window = gtk::ApplicationWindow::new(app);
|
||||||
window.present();
|
window.present();
|
||||||
|
|
||||||
let mut countdown = Duration::from_secs(5 * 60);
|
let mut countdown = Duration::from_secs(5);
|
||||||
let mut next_tick = Instant::now() + Duration::from_secs(1);
|
let mut next_tick = Instant::now() + Duration::from_secs(1);
|
||||||
|
|
||||||
let splash = Splash::new("GTK Kifu".to_owned(), countdown.clone());
|
let splash = Splash::new("GTK Kifu".to_owned(), countdown.clone());
|
||||||
|
|
||||||
window.set_child(Some(&splash));
|
window.set_child(Some(&splash));
|
||||||
|
// window.fullscreen();
|
||||||
|
|
||||||
gtk_rx.attach(None, move |time| {
|
window.connect_maximized_notify(|window| {
|
||||||
splash.set_time(time);
|
window.fullscreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
gtk_rx.attach(None, move |event| {
|
||||||
|
match event {
|
||||||
|
Event::Frames(frames) => splash.tick(frames),
|
||||||
|
Event::Time(time) => splash.set_time(time),
|
||||||
|
};
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
std::thread::spawn(move || loop {
|
std::thread::spawn(move || loop {
|
||||||
std::thread::sleep(Duration::from_secs(1));
|
std::thread::sleep(Duration::from_millis(1000 / 60));
|
||||||
if Instant::now() >= next_tick {
|
let _ = gtk_tx.send(Event::Frames(1));
|
||||||
|
if Instant::now() >= next_tick && countdown > Duration::from_secs(0) {
|
||||||
countdown = countdown - Duration::from_secs(1);
|
countdown = countdown - Duration::from_secs(1);
|
||||||
let _ = gtk_tx.send(countdown);
|
let _ = gtk_tx.send(Event::Time(countdown));
|
||||||
next_tick = next_tick + Duration::from_secs(1);
|
next_tick = next_tick + Duration::from_secs(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue