use cairo::{ Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern, }; use glib::Object; use gtk::{prelude::*, subclass::prelude::*}; use std::{cell::RefCell, rc::Rc}; const WIDTH: i32 = 1920; const HEIGHT: i32 = 1280; pub struct SplashPrivate { text: Rc>, time: Rc>, background: Rc>, } impl SplashPrivate { fn set_text(&self, text: String) { *self.text.borrow_mut() = text; } fn set_time(&self, time: String) { *self.time.borrow_mut() = time; } fn redraw_background(&self) { let background = ImageSurface::create(Format::Rgb24, WIDTH, HEIGHT).unwrap(); let context = Context::new(background).unwrap(); context.push_group(); context.set_source_rgb(0., 0., 0.); let _ = context.paint(); 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(); { 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(); } { context.set_font_size(128.); let time_extents = context.text_extents(&self.time.borrow()).unwrap(); 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(&self.time.borrow()); } let background = context.pop_group().unwrap(); *self.background.borrow_mut() = background; } } #[glib::object_subclass] impl ObjectSubclass for SplashPrivate { const NAME: &'static str = "Splash"; type Type = Splash; type ParentType = gtk::DrawingArea; fn new() -> SplashPrivate { // Set up a default plain black background let background = ImageSurface::create(Format::Rgb24, WIDTH, HEIGHT).unwrap(); let context = Context::new(background).unwrap(); context.push_group(); context.set_source_rgb(0., 0., 0.); let _ = context.paint(); let background = context.pop_group().unwrap(); SplashPrivate { text: Rc::new(RefCell::new(String::from(""))), time: Rc::new(RefCell::new(String::from(""))), background: Rc::new(RefCell::new(background)), } } } impl ObjectImpl for SplashPrivate {} impl WidgetImpl for SplashPrivate {} impl DrawingAreaImpl for SplashPrivate {} glib::wrapper! { pub struct Splash(ObjectSubclass) @extends gtk::DrawingArea, gtk::Widget; } impl Splash { pub fn new(text: String, time: String) -> Self { let s: Self = Object::builder().build(); s.set_width_request(WIDTH); s.set_height_request(HEIGHT); s.imp().set_text(text); s.imp().set_time(time); s.imp().redraw_background(); s.set_draw_func({ let s = s.clone(); move |_, context, _width, _height| { let background = s.imp().background.borrow(); let _ = context.set_source(&*background); let _ = context.paint(); } }); s } } struct AsymLineCutout { orientation: gtk::Orientation, start_x: f64, start_y: f64, start_length: f64, total_length: f64, cutout_length: f64, height: f64, invert: bool, } 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.height, self.start_y + dodge, ); context.line_to( 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.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 + dodge, self.start_y + self.start_length + self.height, ); context.line_to( self.start_x + dodge, self.start_y + self.start_length + self.height + self.cutout_length, ); context.line_to( self.start_x + dodge / 2., self.start_y + self.start_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"), } } } fn main() { let app = gtk::Application::builder() .application_id("com.luminescent-dreams.cyberpunk-splash") .build(); app.connect_activate(move |app| { let window = gtk::ApplicationWindow::new(app); window.present(); window.set_child(Some(&Splash::new("GTK Kifu".to_owned(), "4:23".to_owned()))); }); app.run(); }