monorepo/cyberpunk/src/lib.rs

273 lines
8.3 KiB
Rust

use cairo::{
Context, FontSlant, FontWeight, Format, ImageSurface, LineCap, Pattern,
TextExtents,
};
pub struct AsymLineCutout {
pub orientation: gtk::Orientation,
pub start_x: f64,
pub start_y: f64,
pub start_length: f64,
pub cutout_length: f64,
pub end_length: f64,
pub height: f64,
pub invert: bool,
}
impl AsymLineCutout {
pub fn draw(&self, pen: &impl Pen) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x + self.start_length, self.start_y);
pen.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
pen.line_to(
self.start_x + self.start_length + self.height + self.cutout_length,
self.start_y + dodge,
);
pen.line_to(
self.start_x
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
self.start_y + dodge / 2.,
);
pen.line_to(
self.start_x
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.)
+ self.end_length,
self.start_y + dodge / 2.,
);
}
gtk::Orientation::Vertical => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x, self.start_y + self.start_length);
pen.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height,
);
pen.line_to(
self.start_x + dodge,
self.start_y + self.start_length + self.height + self.cutout_length,
);
pen.line_to(
self.start_x + dodge / 2.,
self.start_y
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.),
);
pen.line_to(
self.start_x + dodge / 2.,
self.start_y
+ self.start_length
+ self.height
+ self.cutout_length
+ (self.height / 2.)
+ self.end_length,
);
}
_ => panic!("unknown orientation"),
}
}
}
// Represents an asymetrical line that starts at one location, then a 45-degree angle and then
// another line afterwards.
pub struct AsymLine {
// Will this be drawn left-to-right or up-to-down?
pub orientation: gtk::Orientation,
// Starting address
pub start_x: f64,
pub start_y: f64,
// Length of the first segment
pub start_length: f64,
// Height to dodge over to the next section
pub height: f64,
// Total length of the entire line.
pub end_length: f64,
// When normal, the angle dodge is upwards. When inverted, the angle dodge is downwards.
pub invert: bool,
}
impl AsymLine {
pub fn draw(&self, pen: &impl Pen) {
let dodge = if self.invert {
self.height
} else {
-self.height
};
match self.orientation {
gtk::Orientation::Horizontal => {
pen.move_to(self.start_x, self.start_y);
pen.line_to(self.start_x + self.start_length, self.start_y);
pen.line_to(
self.start_x + self.start_length + self.height,
self.start_y + dodge,
);
pen.line_to(
self.start_x + self.start_length + self.height + self.end_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 / (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"),
}
}
}
/// Represents a pen for drawing a pattern. This is good for complex patterns that may require
/// multiple identical steps.
pub trait Pen {
/// Move the pen to a location.
fn move_to(&self, x: f64, y: f64);
/// Draw a line from the current location to the specified destination.
fn line_to(&self, x: f64, y: f64);
/// Instantiate the line.
fn stroke(&self);
/// Convert all of the drawing into a pattern that can be painted to a drawing context.
fn finish(self) -> Pattern;
}
pub struct GlowPen {
blur_context: Context,
draw_context: Context,
}
impl GlowPen {
pub fn new(
width: i32,
height: i32,
line_width: f64,
blur_line_width: f64,
color: (f64, f64, f64),
) -> Self {
let blur_context =
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
blur_context.set_line_width(blur_line_width);
blur_context.set_source_rgba(color.0, color.1, color.2, 0.5);
blur_context.push_group();
blur_context.set_line_cap(LineCap::Round);
let draw_context =
Context::new(ImageSurface::create(Format::Rgb24, width, height).unwrap()).unwrap();
draw_context.set_line_width(line_width);
draw_context.set_source_rgb(color.0, color.1, color.2);
draw_context.push_group();
draw_context.set_line_cap(LineCap::Round);
Self {
blur_context,
draw_context,
}
}
}
impl Pen for GlowPen {
fn move_to(&self, x: f64, y: f64) {
self.blur_context.move_to(x, y);
self.draw_context.move_to(x, y);
}
fn line_to(&self, x: f64, y: f64) {
self.blur_context.line_to(x, y);
self.draw_context.line_to(x, y);
}
fn stroke(&self) {
self.blur_context.stroke().expect("to draw the blur line");
self.draw_context
.stroke()
.expect("to draw the regular line");
}
fn finish(self) -> Pattern {
let foreground = self.draw_context.pop_group().unwrap();
self.blur_context.set_source(foreground).unwrap();
self.blur_context.paint().unwrap();
self.blur_context.pop_group().unwrap()
}
}
pub struct Text<'a> {
content: String,
context: &'a Context,
}
impl<'a> Text<'a> {
pub fn new(content: String, context: &'a Context, size: f64) -> Self {
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
context.set_font_size(size);
Self { content, context }
}
pub fn extents(&self) -> TextExtents {
self.context.text_extents(&self.content).unwrap()
}
pub fn draw(&self) {
let _ = self.context.show_text(&self.content);
}
}