Create a slideshow application in my cyberpunk style #252
File diff suppressed because it is too large
Load Diff
|
@ -6,10 +6,10 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cairo-rs = "0.20.1"
|
cairo-rs = "0.18"
|
||||||
cyberpunk = { path = "../cyberpunk" }
|
cyberpunk = { path = "../cyberpunk" }
|
||||||
gio = "0.20.4"
|
gio = "0.18"
|
||||||
glib = "0.20.4"
|
glib = "0.18"
|
||||||
gtk = { version = "0.7", package = "gtk4" }
|
gtk = { version = "0.7", package = "gtk4" }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
serde_yml = "0.0.12"
|
serde_yml = "0.0.12"
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
- text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan
|
- text: The distinguishing thing about magic is that it includes some kind of personal element. The person who is performing the magic is relevant to the magic. -- Ted Chang, Marie Brennan
|
||||||
position: top
|
position: top
|
||||||
transition_in: !Fade
|
transition: !Fade
|
||||||
duration:
|
|
||||||
secs: 5
|
|
||||||
nanos: 0
|
|
||||||
- text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark.
|
- text: Any sufficiently advanced technology is indistinguishable from magic. -- Arthur C. Clark.
|
||||||
position: middle
|
position: middle
|
||||||
transition_in: !Fade
|
transition: !Fade
|
||||||
duration:
|
|
||||||
secs: 5
|
|
||||||
nanos: 0
|
|
||||||
|
|
||||||
- text: Electricity is the closest we get to Magic in this world.
|
- text: Electricity is the closest we get to Magic in this world.
|
||||||
position: middle
|
position: middle
|
||||||
transition_in: !Fade
|
transition: !Fade
|
||||||
duration:
|
|
||||||
secs: 5
|
|
||||||
nanos: 0
|
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,47 @@
|
||||||
use std::{fs::File, io::Read, path::Path, rc::Rc, time::Duration};
|
use std::{cell::RefCell, fs::File, io::Read, ops::Index, path::Path, rc::Rc, time::Duration};
|
||||||
|
|
||||||
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
|
use cyberpunk::{AsymLine, AsymLineCutout, GlowPen, Pen, Text};
|
||||||
use glib::Object;
|
use glib::{GString, Object};
|
||||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
use gtk::{
|
||||||
|
glib::{self, Propagation},
|
||||||
|
prelude::*,
|
||||||
|
subclass::prelude::*,
|
||||||
|
EventControllerKey,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
const PURPLE: (f64, f64, f64) = (0.7, 0., 1.);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
enum TextArea {
|
enum Position {
|
||||||
Top,
|
Top,
|
||||||
Middle,
|
Middle,
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
struct Fade {
|
enum TransitionName {
|
||||||
duration: Duration,
|
Fade,
|
||||||
|
CrossFade,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
struct Fade(Step);
|
||||||
enum AnimationIn {
|
|
||||||
|
enum Transition {
|
||||||
Fade(Fade),
|
Fade(Fade),
|
||||||
|
CrossFade(Step, Step),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
enum AnimationOut {
|
struct Step {
|
||||||
Fade(Fade),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct ScriptElement {
|
|
||||||
text: String,
|
text: String,
|
||||||
position: TextArea,
|
position: Position,
|
||||||
transition_in: AnimationIn,
|
transition: TransitionName,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
struct Script(Vec<ScriptElement>);
|
struct Script(Vec<Step>);
|
||||||
|
|
||||||
impl Script {
|
impl Script {
|
||||||
fn from_file(path: &Path) -> Result<Script, serde_yml::Error> {
|
fn from_file(path: &Path) -> Result<Script, serde_yml::Error> {
|
||||||
|
@ -47,13 +52,106 @@ impl Script {
|
||||||
Ok(Self(script))
|
Ok(Self(script))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a ScriptElement> {
|
fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Step> {
|
||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Script {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Script {
|
||||||
|
type Output = Step;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.0[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CyberScreenState {
|
||||||
|
script: Script,
|
||||||
|
idx: Option<usize>,
|
||||||
|
top: Option<Step>,
|
||||||
|
middle: Option<Step>,
|
||||||
|
bottom: Option<Step>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CyberScreenState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
script: Script(vec![]),
|
||||||
|
idx: None,
|
||||||
|
top: None,
|
||||||
|
middle: None,
|
||||||
|
bottom: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CyberScreenState {
|
||||||
|
fn new(script: Script) -> CyberScreenState {
|
||||||
|
let mut s = CyberScreenState::default();
|
||||||
|
s.script = script;
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_page(&mut self) -> Transition {
|
||||||
|
let idx = match self.idx {
|
||||||
|
None => 0,
|
||||||
|
Some(idx) => if idx < self.script.len() {
|
||||||
|
idx + 1
|
||||||
|
} else {
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.idx = Some(idx);
|
||||||
|
let step = self.script[idx].clone();
|
||||||
|
|
||||||
|
match step.position {
|
||||||
|
Position::Top => {
|
||||||
|
let transition = if let Some(old_step) = self.top.take() {
|
||||||
|
Transition::CrossFade(old_step, step.clone())
|
||||||
|
} else {
|
||||||
|
Transition::Fade(Fade(step.clone()))
|
||||||
|
};
|
||||||
|
self.top = Some(step);
|
||||||
|
transition
|
||||||
|
}
|
||||||
|
Position::Middle => {
|
||||||
|
let transition = if let Some(old_step) = self.middle.take() {
|
||||||
|
Transition::CrossFade(old_step, step.clone())
|
||||||
|
} else {
|
||||||
|
Transition::Fade(Fade(step.clone()))
|
||||||
|
};
|
||||||
|
self.middle = Some(step);
|
||||||
|
transition
|
||||||
|
}
|
||||||
|
Position::Bottom => {
|
||||||
|
let transition = if let Some(old_step) = self.bottom.take() {
|
||||||
|
Transition::CrossFade(old_step, step.clone())
|
||||||
|
} else {
|
||||||
|
Transition::Fade(Fade(step.clone()))
|
||||||
|
};
|
||||||
|
self.bottom = Some(step);
|
||||||
|
transition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CyberScreenPrivate {}
|
pub struct CyberScreenPrivate {
|
||||||
|
state: Rc<RefCell<CyberScreenState>>,
|
||||||
|
transition_queue: Rc<RefCell<Vec<Transition>>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for CyberScreenPrivate {
|
impl ObjectSubclass for CyberScreenPrivate {
|
||||||
|
@ -66,74 +164,89 @@ impl ObjectImpl for CyberScreenPrivate {}
|
||||||
impl WidgetImpl for CyberScreenPrivate {}
|
impl WidgetImpl for CyberScreenPrivate {}
|
||||||
impl DrawingAreaImpl for CyberScreenPrivate {}
|
impl DrawingAreaImpl for CyberScreenPrivate {}
|
||||||
|
|
||||||
|
impl CyberScreenPrivate {
|
||||||
|
fn set_script(&self, script: Script) {
|
||||||
|
*self.state.borrow_mut() = CyberScreenState::new(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_page(&self) {
|
||||||
|
let transition = self.state.borrow_mut().next_page();
|
||||||
|
self.transition_queue.borrow_mut().push(transition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub struct CyberScreen(ObjectSubclass<CyberScreenPrivate>) @extends gtk::DrawingArea, gtk::Widget;
|
pub struct CyberScreen(ObjectSubclass<CyberScreenPrivate>) @extends gtk::DrawingArea, gtk::Widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CyberScreen {
|
impl CyberScreen {
|
||||||
pub fn new() -> Self {
|
pub fn new(script: Script) -> Self {
|
||||||
let s: Self = Object::builder().build();
|
let s: Self = Object::builder().build();
|
||||||
|
s.imp().set_script(script);
|
||||||
|
|
||||||
s.set_draw_func({
|
s.set_draw_func({
|
||||||
|
let s = s.clone();
|
||||||
move |_, context, width, height| {
|
move |_, context, width, height| {
|
||||||
let _ = context.set_source_rgb(0., 0., 0.);
|
let _ = context.set_source_rgb(0., 0., 0.);
|
||||||
let _ = context.paint();
|
let _ = context.paint();
|
||||||
|
|
||||||
let pen = GlowPen::new(width, height, 2., 8., (0.7, 0., 1.));
|
let pen = GlowPen::new(width, height, 2., 8., (0.7, 0., 1.));
|
||||||
|
/*
|
||||||
AsymLine {
|
for i in 0..6 {
|
||||||
orientation: gtk::Orientation::Horizontal,
|
pen.move_to(0., height as f64 * i as f64 / 5.);
|
||||||
start_x: 50.,
|
pen.line_to(width as f64, height as f64 * i as f64 / 5.);
|
||||||
start_y: height as f64 / 2.,
|
|
||||||
start_length: 75.,
|
|
||||||
height: 50.,
|
|
||||||
end_length: 75.,
|
|
||||||
invert: false,
|
|
||||||
}
|
}
|
||||||
.draw(&pen);
|
|
||||||
pen.stroke();
|
pen.stroke();
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
AsymLineCutout {
|
|
||||||
orientation: gtk::Orientation::Horizontal,
|
|
||||||
start_x: 50.,
|
|
||||||
start_y: height as f64 / 4. * 3.,
|
|
||||||
start_length: 75.,
|
|
||||||
cutout_length: 15.,
|
|
||||||
end_length: 75.,
|
|
||||||
height: 15.,
|
|
||||||
invert: true,
|
|
||||||
}.draw(&pen);
|
|
||||||
pen.stroke();
|
|
||||||
|
|
||||||
let tracery = pen.finish();
|
let tracery = pen.finish();
|
||||||
let _ = context.set_source(tracery);
|
let _ = context.set_source(tracery);
|
||||||
let _ = context.paint();
|
let _ = context.paint();
|
||||||
|
|
||||||
let text = Text::new("Test text".to_owned(), &context, 64.);
|
for transition in s.imp().transition_queue.borrow().iter() {
|
||||||
let text_extents = text.extents();
|
if let Some(ref text) = s.imp().state.borrow().top {
|
||||||
context.move_to(20., text_extents.height() + 40.);
|
let text_display = Text::new(text.text.clone(), context, 32.);
|
||||||
context.set_source_rgb(0.7, 0., 1.);
|
let y = height as f64 * 1. / 5. + text_display.extents().height();
|
||||||
text.draw();
|
|
||||||
|
context.move_to(20., y);
|
||||||
|
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2);
|
||||||
|
text_display.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref text) = s.imp().state.borrow().middle {
|
||||||
|
let text_display = Text::new(text.text.clone(), context, 32.);
|
||||||
|
let y = height as f64 * 2. / 5. + text_display.extents().height();
|
||||||
|
|
||||||
|
context.move_to(20., y);
|
||||||
|
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2);
|
||||||
|
text_display.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref text) = s.imp().state.borrow().bottom {
|
||||||
|
let text_display = Text::new(text.text.clone(), context, 32.);
|
||||||
|
let y = height as f64 * 3. / 5. + text_display.extents().height();
|
||||||
|
|
||||||
|
context.move_to(20., y);
|
||||||
|
let _ = context.set_source_rgb(PURPLE.0, PURPLE.1, PURPLE.2);
|
||||||
|
text_display.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_page(&self) {
|
||||||
|
println!("next page");
|
||||||
|
self.imp().next_page();
|
||||||
|
self.queue_draw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
/*
|
|
||||||
let script: Script = Script(vec![ScriptElement {
|
|
||||||
text: "abcdefg".to_owned(),
|
|
||||||
position: TextArea::Top,
|
|
||||||
transition_in: AnimationIn::Fade(Fade {
|
|
||||||
duration: Duration::from_secs(10),
|
|
||||||
}),
|
|
||||||
}]);
|
|
||||||
|
|
||||||
println!("{}", serde_yml::to_string(&script).unwrap());
|
|
||||||
*/
|
|
||||||
|
|
||||||
let script = Script::from_file(Path::new("./script.yml")).unwrap();
|
let script = Script::from_file(Path::new("./script.yml")).unwrap();
|
||||||
for element in script.iter() {
|
for element in script.iter() {
|
||||||
println!("{:?}", element);
|
println!("{:?}", element);
|
||||||
|
@ -143,11 +256,22 @@ fn main() {
|
||||||
.application_id("com.luminescent-dreams.cyberpunk-slideshow")
|
.application_id("com.luminescent-dreams.cyberpunk-slideshow")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
app.connect_activate(|app| {
|
app.connect_activate(move |app| {
|
||||||
let window = gtk::ApplicationWindow::new(app);
|
let screen = CyberScreen::new(script.clone());
|
||||||
window.present();
|
|
||||||
|
|
||||||
let screen = CyberScreen::new();
|
let events = EventControllerKey::new();
|
||||||
|
|
||||||
|
events.connect_key_released({
|
||||||
|
let screen = screen.clone();
|
||||||
|
move |_, key, _, _| {
|
||||||
|
if key.name() == Some(GString::from("Right")) {
|
||||||
|
screen.next_page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let window = gtk::ApplicationWindow::new(app);
|
||||||
|
window.add_controller(events);
|
||||||
|
|
||||||
window.set_child(Some(&screen));
|
window.set_child(Some(&screen));
|
||||||
window.set_width_request(800);
|
window.set_width_request(800);
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
use cairo::{
|
use cairo::{
|
||||||
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, LinearGradient, Pattern,
|
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, Pattern,
|
||||||
TextExtents,
|
TextExtents,
|
||||||
};
|
};
|
||||||
use gtk::{prelude::*, subclass::prelude::*, EventControllerKey};
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{Arc, RwLock},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct AsymLineCutout {
|
pub struct AsymLineCutout {
|
||||||
pub orientation: gtk::Orientation,
|
pub orientation: gtk::Orientation,
|
||||||
|
|
Loading…
Reference in New Issue