302 lines
9.2 KiB
Rust
302 lines
9.2 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: 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
|
|
}
|