added untested tracker state metrics
This commit is contained in:
parent
8164eca473
commit
81a4f04d6b
7 changed files with 180 additions and 46 deletions
|
@ -20,6 +20,7 @@ use crate::remote_sources::TrackerState;
|
||||||
use crate::{gstreamer_pipeline, remote_sources};
|
use crate::{gstreamer_pipeline, remote_sources};
|
||||||
use crate::{joystick_source::joystick_loop, ui::GuiUpdate};
|
use crate::{joystick_source::joystick_loop, ui::GuiUpdate};
|
||||||
|
|
||||||
|
use super::perf_state::TrackerMetrics;
|
||||||
use super::remote_video_processor::remote_video_loop;
|
use super::remote_video_processor::remote_video_loop;
|
||||||
use super::{ApplicationEvent, ConnectionType};
|
use super::{ApplicationEvent, ConnectionType};
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ pub struct SocketState {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CoordState<'a> {
|
pub struct CoordState<'a> {
|
||||||
pub settings: Arc<RwLock<AppConfig>>,
|
pub settings: Arc<RwLock<AppConfig>>,
|
||||||
|
pub tracker_metrics: Arc<tokio::sync::Mutex<TrackerMetrics>>,
|
||||||
|
|
||||||
pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>,
|
pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>,
|
||||||
pub remote_sources_state: Arc<SocketState>,
|
pub remote_sources_state: Arc<SocketState>,
|
||||||
|
@ -60,9 +62,11 @@ impl<'a> CoordState<'a> {
|
||||||
rt: Handle,
|
rt: Handle,
|
||||||
tracker_state: Arc<Mutex<TrackerState>>,
|
tracker_state: Arc<Mutex<TrackerState>>,
|
||||||
settings: Arc<RwLock<AppConfig>>,
|
settings: Arc<RwLock<AppConfig>>,
|
||||||
|
tracker_header: Arc<std::sync::RwLock<String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let this = CoordState {
|
let this = CoordState {
|
||||||
settings,
|
settings,
|
||||||
|
tracker_metrics: Arc::new(tokio::sync::Mutex::new(TrackerMetrics::new(tracker_header))),
|
||||||
|
|
||||||
sck_outbound: None,
|
sck_outbound: None,
|
||||||
stay_alive_sck_recvr: Arc::new(AtomicBool::new(false)),
|
stay_alive_sck_recvr: Arc::new(AtomicBool::new(false)),
|
||||||
|
@ -181,6 +185,7 @@ impl<'a> CoordState<'a> {
|
||||||
self.to_mec.clone(),
|
self.to_mec.clone(),
|
||||||
self.tracker_connection_state.clone(),
|
self.tracker_connection_state.clone(),
|
||||||
self.tracker_state.clone(),
|
self.tracker_state.clone(),
|
||||||
|
self.tracker_metrics.clone(),
|
||||||
self.rt.clone(),
|
self.rt.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ use tracing::{debug, error, info, instrument};
|
||||||
mod coord_state;
|
mod coord_state;
|
||||||
mod process_box_string;
|
mod process_box_string;
|
||||||
mod remote_video_processor;
|
mod remote_video_processor;
|
||||||
|
mod perf_state;
|
||||||
|
|
||||||
use crate::config::AppConfig;
|
use crate::config::AppConfig;
|
||||||
use crate::remote_sources::TrackerState;
|
use crate::remote_sources::TrackerState;
|
||||||
|
@ -56,12 +57,13 @@ pub async fn start_coordinator(
|
||||||
runtime: Handle,
|
runtime: Handle,
|
||||||
tracker_state: Arc<Mutex<TrackerState>>,
|
tracker_state: Arc<Mutex<TrackerState>>,
|
||||||
settings: Arc<RwLock<AppConfig>>,
|
settings: Arc<RwLock<AppConfig>>,
|
||||||
|
tracker_header: Arc<std::sync::RwLock<String>>,
|
||||||
) {
|
) {
|
||||||
info!("Starting coordinator!");
|
info!("Starting coordinator!");
|
||||||
|
|
||||||
let mec = pin!(mec);
|
let mec = pin!(mec);
|
||||||
|
|
||||||
let mut state = CoordState::new(mec, to_mec, to_gui, runtime, tracker_state, settings);
|
let mut state = CoordState::new(mec, to_mec, to_gui, runtime, tracker_state, settings, tracker_header);
|
||||||
|
|
||||||
state
|
state
|
||||||
.pipeline
|
.pipeline
|
||||||
|
|
57
src/coordinator/perf_state.rs
Normal file
57
src/coordinator/perf_state.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use std::{sync, time::Duration, collections::VecDeque};
|
||||||
|
|
||||||
|
const MAX_RECORDED_TIMES: usize = 10;
|
||||||
|
const DEGRADED_TRACKER_TIME: u128 = 100;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrackerMetrics {
|
||||||
|
pub header_text: sync::Arc<sync::RwLock<String>>,
|
||||||
|
tracker_times: VecDeque<u128>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackerMetrics {
|
||||||
|
pub fn new(text_reference: sync::Arc<sync::RwLock<String>>) -> Self {
|
||||||
|
TrackerMetrics {
|
||||||
|
header_text: text_reference,
|
||||||
|
|
||||||
|
tracker_times: VecDeque::with_capacity(MAX_RECORDED_TIMES),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_times(&mut self) {
|
||||||
|
for _ in 0..10 {
|
||||||
|
self.tracker_times.pop_front();
|
||||||
|
}
|
||||||
|
self.insert_time(Duration::new(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_time(&mut self, new_measurement: Duration) {
|
||||||
|
if self.tracker_times.len() > MAX_RECORDED_TIMES {
|
||||||
|
let _ = self.tracker_times.pop_front();
|
||||||
|
}
|
||||||
|
self.tracker_times.push_back(new_measurement.as_millis());
|
||||||
|
|
||||||
|
let avg_time = self.tracker_times.iter().sum::<u128>() / self.tracker_times.len() as u128;
|
||||||
|
|
||||||
|
if avg_time == 0 {
|
||||||
|
if let Ok(mut writer) = self.header_text.write() {
|
||||||
|
writer.clear();
|
||||||
|
writer.push_str(&format!("Status: Failed Avg Response: {} ms", avg_time.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if avg_time > DEGRADED_TRACKER_TIME {
|
||||||
|
if let Ok(mut writer) = self.header_text.write() {
|
||||||
|
writer.clear();
|
||||||
|
writer.push_str(&format!("Status: Degraded Avg Response: {} ms", avg_time.to_string()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Ok(mut writer) = self.header_text.write() {
|
||||||
|
writer.clear();
|
||||||
|
writer.push_str(&format!("Status: Nominal Avg Response: {} ms", avg_time.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ use tracing::{error, info, instrument};
|
||||||
|
|
||||||
use crate::remote_sources::TrackerState;
|
use crate::remote_sources::TrackerState;
|
||||||
|
|
||||||
use super::{process_box_string::process_incoming_string, ApplicationEvent, SocketState};
|
use super::{perf_state::TrackerMetrics, process_box_string::process_incoming_string, ApplicationEvent, SocketState};
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn remote_video_loop(
|
pub async fn remote_video_loop(
|
||||||
|
@ -27,12 +27,14 @@ pub async fn remote_video_loop(
|
||||||
to_mec: Sender<ApplicationEvent>,
|
to_mec: Sender<ApplicationEvent>,
|
||||||
socket_state: Arc<SocketState>,
|
socket_state: Arc<SocketState>,
|
||||||
tracker_state: Arc<Mutex<TrackerState>>,
|
tracker_state: Arc<Mutex<TrackerState>>,
|
||||||
|
tracker_metrics: Arc<tokio::sync::Mutex<TrackerMetrics>>,
|
||||||
runtime: Handle,
|
runtime: Handle,
|
||||||
) {
|
) {
|
||||||
info!(
|
info!(
|
||||||
"Starting remote tracker processing connection to: {}",
|
"Starting remote tracker processing connection to: {}",
|
||||||
conn_string
|
conn_string
|
||||||
);
|
);
|
||||||
|
|
||||||
let video_info =
|
let video_info =
|
||||||
gstreamer_video::VideoInfo::builder(gstreamer_video::VideoFormat::Rgb, 640, 480)
|
gstreamer_video::VideoInfo::builder(gstreamer_video::VideoFormat::Rgb, 640, 480)
|
||||||
.build()
|
.build()
|
||||||
|
@ -55,7 +57,10 @@ pub async fn remote_video_loop(
|
||||||
socket_state.clone(),
|
socket_state.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let mut last_iter: Instant;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
last_iter = Instant::now();
|
||||||
// Do this in an encloser to not keep a lock on the appsink
|
// Do this in an encloser to not keep a lock on the appsink
|
||||||
let video_frame = match {
|
let video_frame = match {
|
||||||
let appsnk = match appsink.lock() {
|
let appsnk = match appsink.lock() {
|
||||||
|
@ -100,6 +105,12 @@ pub async fn remote_video_loop(
|
||||||
info!("Shutting down remote video loop");
|
info!("Shutting down remote video loop");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut tm = tracker_metrics.lock().await;
|
||||||
|
tm.insert_time(Instant::now() - last_iter);
|
||||||
|
}
|
||||||
|
|
||||||
// rate limit updates
|
// rate limit updates
|
||||||
sleep_until(Instant::now() + Duration::from_millis(50)).await;
|
sleep_until(Instant::now() + Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
|
|
78
src/ui/liveview_panel.rs
Normal file
78
src/ui/liveview_panel.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use gtk::{gdk::Paintable, prelude::BoxExt, AspectFrame, Box, DrawingArea, Label, Overlay, Picture};
|
||||||
|
|
||||||
|
|
||||||
|
pub struct LiveViewPanel {
|
||||||
|
top_level: gtk::Box,
|
||||||
|
|
||||||
|
pub tracker_status_label: Label,
|
||||||
|
pub cam_status_label: Label,
|
||||||
|
pub adjustment_label: Label,
|
||||||
|
picture: Picture,
|
||||||
|
overlay: Overlay,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveViewPanel {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
|
||||||
|
let right_box = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.hexpand(true)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let tracker_status_label = Label::builder()
|
||||||
|
.label("No Status Yet".to_string())
|
||||||
|
.can_focus(true)
|
||||||
|
.css_classes(vec!["large-label"])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// let conn_status_label = Label::new(Some(&"No Connection".to_string()));
|
||||||
|
let cam_status_label = Label::builder()
|
||||||
|
.label("No Connection".to_string())
|
||||||
|
.can_focus(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let adjustment_label = Label::builder()
|
||||||
|
.label("X: 0 Y: )")
|
||||||
|
.justify(gtk::Justification::Center)
|
||||||
|
.css_classes(vec!["JoystickCurrent"])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let webcam_picture = gtk::Picture::builder().can_focus(false).build();
|
||||||
|
|
||||||
|
let overlay_box = gtk::Overlay::builder().build();
|
||||||
|
overlay_box.set_child(Some(&webcam_picture));
|
||||||
|
|
||||||
|
let aspect = AspectFrame::builder()
|
||||||
|
.ratio(16.0 / 9.0)
|
||||||
|
.obey_child(false)
|
||||||
|
.child(&overlay_box)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
right_box.append(&tracker_status_label);
|
||||||
|
right_box.append(&aspect);
|
||||||
|
right_box.append(&cam_status_label);
|
||||||
|
right_box.append(&adjustment_label);
|
||||||
|
|
||||||
|
LiveViewPanel {
|
||||||
|
adjustment_label,
|
||||||
|
tracker_status_label,
|
||||||
|
cam_status_label,
|
||||||
|
overlay: overlay_box,
|
||||||
|
picture: webcam_picture,
|
||||||
|
top_level: right_box,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_top_level(&self) -> &Box {
|
||||||
|
&self.top_level
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_drawable(&self, new_drawable: &DrawingArea) {
|
||||||
|
self.overlay.add_overlay(new_drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_paintable(&self, new_paintable: &Paintable) {
|
||||||
|
self.picture.set_paintable(Some(new_paintable));
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use std::time::Instant;
|
||||||
use gtk::cairo::Context;
|
use gtk::cairo::Context;
|
||||||
use gtk::gdk::Paintable;
|
use gtk::gdk::Paintable;
|
||||||
use gtk::glib::clone;
|
use gtk::glib::clone;
|
||||||
use gtk::{gio, glib, prelude::*, AspectFrame, CssProvider, Label};
|
use gtk::{gio, glib, prelude::*, CssProvider};
|
||||||
use gtk::{Application, ApplicationWindow};
|
use gtk::{Application, ApplicationWindow};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
|
@ -17,8 +17,10 @@ use crate::remote_sources::TrackerState;
|
||||||
|
|
||||||
mod control_panel;
|
mod control_panel;
|
||||||
mod settings_modal;
|
mod settings_modal;
|
||||||
|
mod liveview_panel;
|
||||||
|
|
||||||
use control_panel::ControlPanel;
|
use control_panel::ControlPanel;
|
||||||
|
use liveview_panel::LiveViewPanel;
|
||||||
|
|
||||||
pub enum GuiUpdate {
|
pub enum GuiUpdate {
|
||||||
SocketDisconnected,
|
SocketDisconnected,
|
||||||
|
@ -115,12 +117,6 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
||||||
.orientation(gtk::Orientation::Vertical)
|
.orientation(gtk::Orientation::Vertical)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let right_box = gtk::Box::builder()
|
|
||||||
.orientation(gtk::Orientation::Vertical)
|
|
||||||
.hexpand(true)
|
|
||||||
.valign(gtk::Align::Center)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Create a window
|
// Create a window
|
||||||
let window = ApplicationWindow::builder()
|
let window = ApplicationWindow::builder()
|
||||||
.application(app)
|
.application(app)
|
||||||
|
@ -157,6 +153,8 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
||||||
|
|
||||||
let coord_config = config.clone();
|
let coord_config = config.clone();
|
||||||
|
|
||||||
|
let tracker_header = Arc::new(std::sync::RwLock::new(String::new()));
|
||||||
|
|
||||||
runtime.spawn(start_coordinator(
|
runtime.spawn(start_coordinator(
|
||||||
mec,
|
mec,
|
||||||
to_mec.clone(),
|
to_mec.clone(),
|
||||||
|
@ -164,42 +162,19 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
||||||
runtime.clone(),
|
runtime.clone(),
|
||||||
tracker_state.clone(),
|
tracker_state.clone(),
|
||||||
coord_config,
|
coord_config,
|
||||||
|
tracker_header.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
// let conn_status_label = Label::new(Some(&"No Connection".to_string()));
|
|
||||||
let conn_status_label = Label::builder()
|
|
||||||
.label("No Connection".to_string())
|
|
||||||
.can_focus(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let control_panel = Arc::new(ControlPanel::new(tracker_state.clone()));
|
let control_panel = Arc::new(ControlPanel::new(tracker_state.clone()));
|
||||||
control_panel.connect_button_callbacks(to_mec.clone());
|
control_panel.connect_button_callbacks(to_mec.clone());
|
||||||
|
|
||||||
let axis_label = Label::builder()
|
|
||||||
.label("X: 0 Y: )")
|
|
||||||
.justify(gtk::Justification::Center)
|
|
||||||
.css_classes(vec!["JoystickCurrent"])
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// left_box.append(&conn_status_label);
|
// left_box.append(&conn_status_label);
|
||||||
left_box.append(control_panel.get_top_level());
|
left_box.append(control_panel.get_top_level());
|
||||||
|
|
||||||
main_box.append(&left_box);
|
main_box.append(&left_box);
|
||||||
|
|
||||||
let webcam_picture = gtk::Picture::builder().can_focus(false).build();
|
let liveview_panel = LiveViewPanel::new();
|
||||||
|
main_box.append(liveview_panel.get_top_level());
|
||||||
let overlay_box = gtk::Overlay::builder().build();
|
|
||||||
let aspect = AspectFrame::builder()
|
|
||||||
.ratio(16.0 / 9.0)
|
|
||||||
.obey_child(false)
|
|
||||||
.child(&overlay_box)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
right_box.append(&aspect);
|
|
||||||
right_box.append(&conn_status_label);
|
|
||||||
right_box.append(&axis_label);
|
|
||||||
|
|
||||||
main_box.append(&right_box);
|
|
||||||
|
|
||||||
let drawable = gtk::DrawingArea::builder().build();
|
let drawable = gtk::DrawingArea::builder().build();
|
||||||
|
|
||||||
|
@ -208,8 +183,7 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
||||||
draw_boxes(width, height, &ctx, &drawable_ts);
|
draw_boxes(width, height, &ctx, &drawable_ts);
|
||||||
});
|
});
|
||||||
|
|
||||||
overlay_box.set_child(Some(&webcam_picture));
|
liveview_panel.set_drawable(&drawable);
|
||||||
overlay_box.add_overlay(&drawable);
|
|
||||||
|
|
||||||
let items = control_panel.items.clone();
|
let items = control_panel.items.clone();
|
||||||
let id_label = control_panel.current_id.clone();
|
let id_label = control_panel.current_id.clone();
|
||||||
|
@ -248,42 +222,45 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
||||||
);
|
);
|
||||||
|
|
||||||
glib::spawn_future_local(
|
glib::spawn_future_local(
|
||||||
glib::clone!(@weak axis_label, @weak conn_status_label, @weak drawable => async move {
|
glib::clone!(@weak drawable => async move {
|
||||||
while let Ok(d) = gui_recv.recv().await {
|
while let Ok(d) = gui_recv.recv().await {
|
||||||
drawable.queue_draw();
|
drawable.queue_draw();
|
||||||
|
if let Ok(reader) = tracker_header.read() {
|
||||||
|
liveview_panel.tracker_status_label.set_text(reader.as_str());
|
||||||
|
}
|
||||||
match d {
|
match d {
|
||||||
GuiUpdate::MoveEvent(msg) => {
|
GuiUpdate::MoveEvent(msg) => {
|
||||||
axis_label.set_text(
|
liveview_panel.adjustment_label.set_text(
|
||||||
format!("X: {:>4} Y: {:>4}", msg.x, msg.y).as_str()
|
format!("X: {:>4} Y: {:>4}", msg.x, msg.y).as_str()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
GuiUpdate::SocketConnected => {
|
GuiUpdate::SocketConnected => {
|
||||||
control_panel.connection_buttons.camera_connection.set_sensitive(true);
|
control_panel.connection_buttons.camera_connection.set_sensitive(true);
|
||||||
control_panel.connection_buttons.camera_connection.set_label("Press to Disconnect");
|
control_panel.connection_buttons.camera_connection.set_label("Press to Disconnect");
|
||||||
conn_status_label.set_label("Connected");
|
liveview_panel.cam_status_label.set_label("Connected");
|
||||||
|
|
||||||
conn_status_label.set_css_classes(&["YesConnection"]);
|
liveview_panel.cam_status_label.set_css_classes(&["YesConnection"]);
|
||||||
|
|
||||||
},
|
},
|
||||||
GuiUpdate::SocketConnecting => {
|
GuiUpdate::SocketConnecting => {
|
||||||
control_panel.connection_buttons.camera_connection.set_sensitive(false);
|
control_panel.connection_buttons.camera_connection.set_sensitive(false);
|
||||||
control_panel.connection_buttons.camera_connection.set_label("Please wait");
|
control_panel.connection_buttons.camera_connection.set_label("Please wait");
|
||||||
conn_status_label.set_label("Connecting");
|
liveview_panel.cam_status_label.set_label("Connecting");
|
||||||
|
|
||||||
conn_status_label.set_css_classes(&["LoadingConnection"]);
|
liveview_panel.cam_status_label.set_css_classes(&["LoadingConnection"]);
|
||||||
|
|
||||||
},
|
},
|
||||||
GuiUpdate::SocketDisconnected => {
|
GuiUpdate::SocketDisconnected => {
|
||||||
control_panel.connection_buttons.camera_connection.set_sensitive(true);
|
control_panel.connection_buttons.camera_connection.set_sensitive(true);
|
||||||
control_panel.connection_buttons.camera_connection.set_label("Press to Connect to Camera");
|
control_panel.connection_buttons.camera_connection.set_label("Press to Connect to Camera");
|
||||||
conn_status_label.set_label("Not Connected to Camera");
|
liveview_panel.cam_status_label.set_label("Not Connected to Camera");
|
||||||
|
|
||||||
conn_status_label.set_css_classes(&["NoConnection"]);
|
liveview_panel.cam_status_label.set_css_classes(&["NoConnection"]);
|
||||||
}
|
}
|
||||||
GuiUpdate::UpdatePaintable(sink) => {
|
GuiUpdate::UpdatePaintable(sink) => {
|
||||||
let paintable = sink.property::<Paintable>("paintable");
|
let paintable = sink.property::<Paintable>("paintable");
|
||||||
|
|
||||||
webcam_picture.set_paintable(Some(&paintable));
|
liveview_panel.set_paintable(&paintable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ label.current-id {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.large-label {
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
entry {
|
entry {
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue