monorepo/cyberpunk/src/lib.rs

302 lines
9.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"),
}
}
}
pub struct SlashMeter {
pub orientation: gtk::Orientation,
pub start_x: f64,
pub start_y: f64,
pub count: u8,
pub fill_count: u8,
pub height: f64,
pub length: f64,
}
impl SlashMeter {
pub 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: Vec<String>,
context: &'a Context,
}
impl<'a> Text<'a> {
pub fn new(content: String, context: &'a Context, size: f64, width: f64) -> Self {
context.select_font_face("Alegreya Sans SC", FontSlant::Normal, FontWeight::Bold);
context.set_font_size(size);
let lines = word_wrap(content, context, width);
Self { content: lines, context }
}
pub fn extents(&self) -> TextExtents {
self.context.text_extents(&self.content[0]).unwrap()
}
pub fn draw(&self) {
let mut baseline = 0.;
for line in self.content.iter() {
baseline += self.context.text_extents(line).unwrap().height() + 10.;
self.context.move_to(0., baseline);
let _ = self.context.show_text(&line);
}
}
}
fn word_wrap(content: String, context: &Context, max_width: f64) -> Vec<String> {
let mut lines = vec![];
let words: Vec<&str> = content.split_whitespace().collect();
let mut start: usize = 0;
let mut line = String::new();
for idx in 0..words.len() + 1 {
line = words[start..idx].join(" ");
let extents = context.text_extents(&line).unwrap();
if extents.width() > max_width {
let line = words[start..idx-1].join(" ");
start = idx-1;
lines.push(line.clone());
}
}
if line.len() > 0 {
lines.push(line);
}
lines
}