#![cfg_attr(rustfmt, rustfmt_skip)]
use std::cell::RefCell;
use std::rc::Rc;
use fltk::app::Scheme;
use fltk::enums::{Align, CallbackTrigger, Color, Event, FrameType, Shortcut};
use fltk::frame::Frame;
use fltk::group::{Group, Pack, PackType};
use fltk::menu::{MenuBar, MenuFlag};
use fltk::{prelude::*, *};
use regexpr::Regex;
struct Match {
offset: usize,
str: String,
}
struct State {
regex: Regex,
matches: Vec<Match>,
}
#[derive(Clone)]
struct Splitter {
left: text::TextEditor,
right: Rc<RefCell<group::Scroll>>,
divider: frame::Frame,
dragging: bool,
state: Rc<RefCell<State>>,
regex_input: input::Input,
}
impl Splitter {
fn new(x: i32, y: i32, w: i32, h: i32, state: Rc<RefCell<State>>) -> Self {
let mut left = text::TextEditor::new(x, y, w / 2 - 2, h, None);
left.set_buffer(text::TextBuffer::default());
left.set_trigger(CallbackTrigger::Changed);
let divider = frame::Frame::new(x + w / 2 - 2, y, 4, h, None);
let mut right = group::Scroll::new(x + w / 2 + 2, y, w / 2 - 2, h, None);
right.set_frame(enums::FrameType::NoBox);
right.set_scrollbar_size(20);
right.clear();
right.end();
let group = Group::new(10, MENU_HEIGHT, w - 20, REGEX_HEIGHT, "regex");
let mut regex_input = input::Input::new(10, MENU_HEIGHT, w - 20 - 50, REGEX_HEIGHT, "");
let mut btn = button::Button::new(10 + w - 20 - 50 + 5, MENU_HEIGHT, 40, REGEX_HEIGHT, "");
btn.set_label("match");
group.end();
regex_input.set_trigger(CallbackTrigger::Changed);
let spl = Self {
left,
right: Rc::new(RefCell::new(right)),
divider,
state,
regex_input,
dragging: false,
};
btn.set_callback({
let spl = spl.clone();
move |_| {
let regex = spl.regex_input.value();
let regex = Regex::compile(®ex).unwrap();
spl.state.borrow_mut().regex = regex;
spl.process();
}
});
spl
}
fn handle_event(&mut self, ev: Event) -> bool {
match ev {
Event::Push => {
self.dragging = true;
true
}
Event::Drag if self.dragging => {
let x = app::event_coords().0;
let min_x = self.left.x() + 50;
let mut scroll = self.right.borrow_mut();
let max_x = scroll.x() + scroll.w() - 50;
if x >= min_x && x <= max_x {
let left_w = x - self.left.x();
let right_w = scroll.x() + scroll.w() - x - 4;
self.left.resize(self.left.x(), self.left.y(), left_w, self.left.h());
self.divider.resize(x, self.divider.y(), 4, self.divider.h());
let scry = scroll.y();
let scrh = scroll.h();
scroll.resize(x + 4, scry, right_w, scrh);
self.left.redraw();
self.divider.redraw();
scroll.redraw();
}
true
}
Event::Released => {
self.dragging = false;
true
}
_ => false,
}
}
fn process(&self) {
if let Some(buf) = self.left.buffer() {
let text = buf.text();
let mut state = self.state.borrow_mut();
let State { regex, matches } = &mut *state;
matches.clear();
matches.extend(
regex.find_matches(&text).map(|m| {
Match { offset: m.span().0, str: m.slice().to_string() }
})
);
let mut scroll = self.right.borrow_mut();
let width = scroll.width() - scroll.scrollbar_size() - 20;
let mut pack = Pack::new(5, 10, width, 0, "");
pack.set_spacing(5);
pack.set_type(PackType::Vertical);
for m in matches {
let label_text = format!("[{}:{}] {}", m.offset, m.offset + m.str.len(), m.str);
let mut frame = Frame::new(0, 0, width, 25, Some(&*label_text));
frame.set_callback({
let (x, y) = (scroll.x(), scroll.y());
move |_| {
let mut f = Frame::new(x - 50, y + 40, 300, 500, "Info");
f.redraw();
}
});
frame.set_frame(FrameType::DownBox);
frame.set_color(Color::from_rgb(240, 240, 255));
frame.set_label_size(14);
frame.set_align(Align::Left | Align::Inside);
pack.add(&frame);
}
pack.end();
scroll.clear();
scroll.add(&pack);
scroll.redraw();
}
}
}
const MENU_HEIGHT: i32 = 25;
const INITIAL_WIDTH: i32 = 800;
const INITIAL_HEIGHT: i32 = 600;
const REGEX_HEIGHT: i32 = 30;
const STATUS_HEIGHT: i32 = 25;
fn menu_bar() {
let mut menu = MenuBar::new(0, 0, INITIAL_WIDTH, MENU_HEIGHT, "");
menu.add(
"File/Exit",
Shortcut::None,
MenuFlag::Normal,
move |_| {
std::process::exit(0);
}
);
}
pub fn start_gui() -> Result<(), String> {
let app = app::App::default().with_scheme(Scheme::Base);
let mut win = window::Window::default()
.with_size(INITIAL_WIDTH, INITIAL_HEIGHT)
.with_label("Regexpr")
.center_screen();
let state = State {
regex: Regex::compile("").unwrap(),
matches: vec![],
};
let state = Rc::new(RefCell::new(state));
let splitter = Splitter::new(
10,
MENU_HEIGHT + REGEX_HEIGHT,
INITIAL_WIDTH - 10,
INITIAL_HEIGHT - MENU_HEIGHT - STATUS_HEIGHT - REGEX_HEIGHT,
Rc::clone(&state),
);
menu_bar();
win.handle({
let mut splt = splitter.clone();
move |_, ev| splt.handle_event(ev)
});
win.resizable(&splitter.left);
win.end();
win.show();
app.run().map_err(|err| {
format!("Couldn't start GUI: {err}")
})
}