feat: Tasten am Stift belegen (Issue #23)
- Im ☰-Menü unter Eingabe → „Tasten am Stift“: je Taste Radieren (Standard), Auswählen, Textmarker, Stift oder Nichts; gilt, solange die Taste gedrückt ist - Tasten werden beim ersten Drücken gelernt und in ui.json gemerkt; die gerade gedrückte leuchtet im Menü grün - Mit einer Werkzeug-Taste geschriebene Striche benutzen dieses Werkzeug, danach ist wieder das gewählte Werkzeug aktiv Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,8 @@ Getestet wird vor allem unter Zorin OS 18 / Ubuntu 24.04 (Wayland und X11).
|
||||
- **Eigene Farben:** Jede Farbe in der Werkzeugleiste (Stifte, Textmarker, Text) lässt sich anpassen – gewählte Farbe erneut antippen, eine Vorgabe oder mit 🎨 einen freien Farbton wählen. Die Leiste merkt sich die Farben. Am Bildschirm erscheinen sie genauso wie im PDF- und Bildexport.
|
||||
- **Glättung in zwei Stufen:** Während des Schreibens glättet ein 1€-Filter das Zittern, die Linie erscheint sofort. Beim Absetzen wird der Strich nachbearbeitet: Ecken (Knicke über 65°) bleiben erhalten, der Druckverlauf wird geglättet, überflüssige Punkte entfallen, die Enden werden rund.
|
||||
- **Druckkurve** im ☰-Menü an den eigenen Stift anpassen (weicher/härter, Schwelle gegen Grundrauschen).
|
||||
- **Radierer** in drei Modi: ganze Striche, nur den überstrichenen Teil (Teilstrich) oder nur Textmarker. Stiftrückseite und Seitentaste des Stifts radieren ebenfalls.
|
||||
- **Tasten am Stift** im ☰-Menü belegen: Radieren (Standard), Auswählen, Textmarker, Stift oder nichts – gilt, solange die Taste gedrückt ist. Jede Taste erscheint dort nach dem ersten Drücken.
|
||||
- **Radierer** in drei Modi: ganze Striche, nur den überstrichenen Teil (Teilstrich) oder nur Textmarker. Die Stiftrückseite radiert ebenfalls.
|
||||
- **Virtuelles Lineal** (`L`): verschieben, am Griff drehen (rastet bei 15° ein); Striche entlang einer Kante werden gerade.
|
||||
|
||||
### Formerkennung
|
||||
|
||||
+87
-24
@@ -30,11 +30,39 @@ use ink_notes::platform::{self, DisplayBackend};
|
||||
use ink_notes::selection::{
|
||||
render_selection_overlay, DragMode, SelectionHandleHit, SelectionState,
|
||||
};
|
||||
use ink_notes::settings::{SidebarSide, UiSettings};
|
||||
use ink_notes::settings::{
|
||||
SidebarSide, StylusAction, StylusButton, UiSettings, MAX_STYLUS_BUTTONS,
|
||||
};
|
||||
use ink_notes::storage::{self, NotebookManifest};
|
||||
use ink_notes::ui::{self, ActiveTool, UiAction, UiState};
|
||||
use ink_notes::updater::UpdaterController;
|
||||
|
||||
/// Wählt `tool` und stellt Farbe und Stärke des Live-Strichs passend ein.
|
||||
fn activate_tool(tool: ActiveTool, ui_state: &mut UiState, canvas: &mut CanvasState) {
|
||||
ui_state.active_tool = tool;
|
||||
match tool {
|
||||
ActiveTool::Pen | ActiveTool::Shape => {
|
||||
canvas.set_stroke_color(ui_state.pen_color);
|
||||
canvas.set_stroke_width_mm(ui_state.pen_stroke_width_mm);
|
||||
}
|
||||
ActiveTool::Highlighter => {
|
||||
canvas.set_stroke_color(ui_state.highlighter_color);
|
||||
canvas.set_stroke_width_mm(ui_state.highlighter_stroke_width_mm);
|
||||
}
|
||||
ActiveTool::Eraser | ActiveTool::Select | ActiveTool::Text => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kennung einer Stifttaste, die über Programmstarts hinweg gleich bleibt (die Tablet-
|
||||
/// Schnittstelle liefert nur eine undurchsichtige, aber vergleichbare Kennung).
|
||||
fn stylus_button_key(id: &octotablet::tool::ButtonID) -> u64 {
|
||||
use std::hash::{Hash, Hasher};
|
||||
// DefaultHasher::new() verwendet feste Schlüssel: gleiche Taste, gleicher Wert
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
id.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Liest Notizbücher und Ordner für den Dokumenten-Manager neu ein.
|
||||
fn refresh_notebooks(base_dir: &Path, ui_state: &mut UiState) {
|
||||
ui_state.available_notebooks = storage::list_notebooks(base_dir);
|
||||
@@ -1107,6 +1135,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
ui_state.pressure_curve = ui_settings.pressure_curve;
|
||||
ui_state.continuous_scroll = ui_settings.continuous_scroll;
|
||||
ui_state.pen_presets = ui_settings.pen_presets;
|
||||
ui_state.stylus_buttons = ui_settings.stylus_buttons.clone();
|
||||
ui_state.select_pen(ui_settings.active_pen);
|
||||
ui_state.highlighter_colors = ui_settings.highlighter_colors;
|
||||
ui_state.select_highlighter_color(ui_settings.active_highlighter);
|
||||
@@ -1186,7 +1215,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut is_stylus_selecting = false;
|
||||
let mut is_stylus_in_proximity = false;
|
||||
let mut stylus_tool_id: Option<octotablet::tool::ID> = None;
|
||||
let mut stylus_barrel_pressed = false;
|
||||
// Gedrückte Stifttaste (Kennung, Belegung) und das Werkzeug, das während eines Strichs mit
|
||||
// einer Werkzeug-Taste (Auswählen, Textmarker, Stift) vertreten wird
|
||||
let mut stylus_held: Option<(u64, StylusAction)> = None;
|
||||
let mut tool_before_button: Option<ActiveTool> = None;
|
||||
let mut stylus_screen_pos = (0.0_f32, 0.0_f32);
|
||||
// Stift hat auf der UI aufgesetzt: alle Ereignisse bis zum Abheben gehen an egui
|
||||
let mut stylus_ui_capture = false;
|
||||
@@ -1357,17 +1389,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state);
|
||||
window.request_redraw();
|
||||
}
|
||||
if let Some(tool) = tool_before_button.take() {
|
||||
activate_tool(tool, &mut ui_state, &mut canvas);
|
||||
}
|
||||
stylus_held = None;
|
||||
ui_state.pressed_stylus_button = None;
|
||||
}
|
||||
}
|
||||
octotablet::events::ToolEvent::Button { pressed, .. } => {
|
||||
stylus_barrel_pressed = pressed;
|
||||
if pressed && is_stylus_drawing {
|
||||
octotablet::events::ToolEvent::Button { button_id, pressed } => {
|
||||
// Tasten werden beim ersten Drücken gelernt (Standard: Radieren)
|
||||
let key = stylus_button_key(&button_id);
|
||||
let mut index = ui_state.stylus_buttons.iter().position(|b| b.id == key);
|
||||
if index.is_none() && pressed && ui_state.stylus_buttons.len() < MAX_STYLUS_BUTTONS {
|
||||
ui_state.stylus_buttons.push(StylusButton { id: key, action: StylusAction::Erase });
|
||||
index = Some(ui_state.stylus_buttons.len() - 1);
|
||||
ui_settings.stylus_buttons = ui_state.stylus_buttons.clone();
|
||||
save_ui_settings(&ui_settings, ui_settings_path.as_deref(), &mut ui_state);
|
||||
}
|
||||
let action = index.map_or(StylusAction::Erase, |i| ui_state.stylus_buttons[i].action);
|
||||
if pressed {
|
||||
stylus_held = Some((key, action));
|
||||
ui_state.pressed_stylus_button = index;
|
||||
if action == StylusAction::Erase && is_stylus_drawing {
|
||||
is_stylus_drawing = false;
|
||||
is_drawing = false;
|
||||
canvas.cancel_stroke();
|
||||
is_erasing = true;
|
||||
window.request_redraw();
|
||||
} else if !pressed
|
||||
}
|
||||
} else if stylus_held.is_some_and(|(held, _)| held == key) {
|
||||
stylus_held = None;
|
||||
ui_state.pressed_stylus_button = None;
|
||||
if action == StylusAction::Erase
|
||||
&& is_erasing
|
||||
&& !tool_is_hardware_eraser
|
||||
&& ui_state.active_tool != ActiveTool::Eraser
|
||||
@@ -1375,6 +1427,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
is_erasing = false;
|
||||
}
|
||||
}
|
||||
window.request_redraw();
|
||||
}
|
||||
octotablet::events::ToolEvent::Pose(pose) => {
|
||||
is_stylus_in_proximity = true;
|
||||
let sx = pose.position[0] * scale;
|
||||
@@ -1471,8 +1525,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
focus_page_at!((sx, sy));
|
||||
// Werkzeug-Taste gedrückt: für diesen Strich dieses Werkzeug
|
||||
let button_action = stylus_held.map(|(_, action)| action);
|
||||
let button_tool = match button_action {
|
||||
Some(StylusAction::Select) => Some(ActiveTool::Select),
|
||||
Some(StylusAction::Highlighter) => Some(ActiveTool::Highlighter),
|
||||
Some(StylusAction::Pen) => Some(ActiveTool::Pen),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(tool) = button_tool
|
||||
.filter(|&t| !tool_is_hardware_eraser && t != ui_state.active_tool)
|
||||
{
|
||||
tool_before_button = Some(ui_state.active_tool);
|
||||
activate_tool(tool, &mut ui_state, &mut canvas);
|
||||
}
|
||||
let should_erase = tool_is_hardware_eraser
|
||||
|| stylus_barrel_pressed
|
||||
|| button_action == Some(StylusAction::Erase)
|
||||
|| ui_state.active_tool == ActiveTool::Eraser;
|
||||
|
||||
if !should_erase && ui_state.active_tool == ActiveTool::Text {
|
||||
@@ -1550,6 +1618,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
let restore_tool = tool_before_button.take();
|
||||
|
||||
if is_stylus_drawing {
|
||||
is_stylus_drawing = false;
|
||||
@@ -1584,6 +1653,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
snapped_shape,
|
||||
);
|
||||
}
|
||||
// Nach einem Strich mit Werkzeug-Taste wieder das gewählte Werkzeug
|
||||
if let Some(tool) = restore_tool {
|
||||
activate_tool(tool, &mut ui_state, &mut canvas);
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -2731,22 +2805,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
for action in actions {
|
||||
match action {
|
||||
UiAction::SetTool(tool) => {
|
||||
ui_state.active_tool = tool;
|
||||
match tool {
|
||||
ActiveTool::Pen => {
|
||||
canvas.set_stroke_color(ui_state.pen_color);
|
||||
canvas.set_stroke_width_mm(ui_state.pen_stroke_width_mm);
|
||||
}
|
||||
ActiveTool::Highlighter => {
|
||||
canvas.set_stroke_color(ui_state.highlighter_color);
|
||||
canvas.set_stroke_width_mm(ui_state.highlighter_stroke_width_mm);
|
||||
}
|
||||
ActiveTool::Shape => {
|
||||
canvas.set_stroke_color(ui_state.pen_color);
|
||||
canvas.set_stroke_width_mm(ui_state.pen_stroke_width_mm);
|
||||
}
|
||||
ActiveTool::Eraser | ActiveTool::Select | ActiveTool::Text => {}
|
||||
}
|
||||
activate_tool(tool, &mut ui_state, &mut canvas);
|
||||
update_window_title(&window, ¬ebook, &manifest, tool, autosave.status());
|
||||
window.request_redraw();
|
||||
}
|
||||
@@ -3433,6 +3492,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
save_ui_settings(&ui_settings, ui_settings_path.as_deref(), &mut ui_state);
|
||||
window.request_redraw();
|
||||
}
|
||||
UiAction::SaveStylusButtons => {
|
||||
ui_settings.stylus_buttons = ui_state.stylus_buttons.clone();
|
||||
save_ui_settings(&ui_settings, ui_settings_path.as_deref(), &mut ui_state);
|
||||
}
|
||||
UiAction::SavePressureCurve(curve) => {
|
||||
ui_settings.pressure_curve = curve;
|
||||
save_ui_settings(&ui_settings, ui_settings_path.as_deref(), &mut ui_state);
|
||||
|
||||
@@ -150,6 +150,53 @@ fn sanitized_color(color: [f32; 4], fallback: [f32; 4]) -> [f32; 4] {
|
||||
out
|
||||
}
|
||||
|
||||
/// Was eine Taste am Stift bewirkt, solange sie gedrückt ist.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub enum StylusAction {
|
||||
/// Radieren (Standard, wie bisher)
|
||||
#[default]
|
||||
Erase,
|
||||
/// Auswählen und verschieben
|
||||
Select,
|
||||
/// Mit dem Textmarker schreiben
|
||||
Highlighter,
|
||||
/// Mit dem gewählten Stift schreiben (z. B. während der Textmarker aktiv ist)
|
||||
Pen,
|
||||
/// Taste nicht verwenden
|
||||
Nothing,
|
||||
}
|
||||
|
||||
impl StylusAction {
|
||||
pub const ALL: [StylusAction; 5] = [
|
||||
StylusAction::Erase,
|
||||
StylusAction::Select,
|
||||
StylusAction::Highlighter,
|
||||
StylusAction::Pen,
|
||||
StylusAction::Nothing,
|
||||
];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
StylusAction::Erase => "Radieren",
|
||||
StylusAction::Select => "Auswählen",
|
||||
StylusAction::Highlighter => "Textmarker",
|
||||
StylusAction::Pen => "Stift",
|
||||
StylusAction::Nothing => "Nichts",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Eine Taste am Stift: Kennung (aus der Tablet-Schnittstelle, beim ersten Drücken gelernt)
|
||||
/// und ihre Belegung.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct StylusButton {
|
||||
pub id: u64,
|
||||
pub action: StylusAction,
|
||||
}
|
||||
|
||||
/// Höchstens so viele Stifttasten werden gemerkt.
|
||||
pub const MAX_STYLUS_BUTTONS: usize = 4;
|
||||
|
||||
/// Benutzereinstellungen der Oberfläche.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
@@ -173,6 +220,8 @@ pub struct UiSettings {
|
||||
pub active_highlighter: usize,
|
||||
pub text_colors: [[f32; 4]; 5],
|
||||
pub active_text_color: usize,
|
||||
/// Tasten am Stift in der Reihenfolge, in der sie zuerst gedrückt wurden
|
||||
pub stylus_buttons: Vec<StylusButton>,
|
||||
}
|
||||
|
||||
impl Default for UiSettings {
|
||||
@@ -190,6 +239,7 @@ impl Default for UiSettings {
|
||||
active_highlighter: 0,
|
||||
text_colors: DEFAULT_TEXT_COLORS,
|
||||
active_text_color: 0,
|
||||
stylus_buttons: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,6 +287,7 @@ impl UiSettings {
|
||||
settings.active_text_color = settings
|
||||
.active_text_color
|
||||
.min(settings.text_colors.len() - 1);
|
||||
settings.stylus_buttons.truncate(MAX_STYLUS_BUTTONS);
|
||||
settings
|
||||
}
|
||||
|
||||
@@ -277,6 +328,16 @@ mod tests {
|
||||
active_highlighter: 3,
|
||||
text_colors: [[0.3, 0.1, 0.6, 1.0]; 5],
|
||||
active_text_color: 4,
|
||||
stylus_buttons: vec![
|
||||
StylusButton {
|
||||
id: 17,
|
||||
action: StylusAction::Select,
|
||||
},
|
||||
StylusButton {
|
||||
id: 42,
|
||||
action: StylusAction::Nothing,
|
||||
},
|
||||
],
|
||||
};
|
||||
settings.save_to(&path).unwrap();
|
||||
assert_eq!(UiSettings::load_from(&path), settings);
|
||||
|
||||
@@ -288,6 +288,8 @@ pub enum UiAction {
|
||||
theme: PaperTheme,
|
||||
adapt_ink: bool,
|
||||
},
|
||||
/// Belegung der Stifttasten speichern (gilt sofort über `UiState::stylus_buttons`)
|
||||
SaveStylusButtons,
|
||||
/// Druckkurve speichern (gilt während des Ziehens bereits über `UiState::pressure_curve`)
|
||||
SavePressureCurve(PressureCurve),
|
||||
/// Seiten durchgehend untereinander statt einzeln anzeigen (wird gespeichert)
|
||||
@@ -376,6 +378,9 @@ pub struct UiState {
|
||||
pub show_color_picker: bool,
|
||||
/// Kontextmenü nach langem Drücken (Einfügen an dieser Stelle)
|
||||
pub context_menu: Option<ContextMenu>,
|
||||
/// Tasten am Stift und ihre Belegung; die gerade gedrückte (Index) wird hervorgehoben
|
||||
pub stylus_buttons: Vec<crate::settings::StylusButton>,
|
||||
pub pressed_stylus_button: Option<usize>,
|
||||
/// Rückfrage „Seite löschen?“ im ☰-Menü ist offen
|
||||
pub confirm_delete_page: bool,
|
||||
/// Seitenzahl der zuletzt gelöschten Seite, solange sie wiederhergestellt werden kann
|
||||
@@ -471,6 +476,8 @@ impl Default for UiState {
|
||||
is_text_color_menu_open: false,
|
||||
show_color_picker: false,
|
||||
context_menu: None,
|
||||
stylus_buttons: Vec::new(),
|
||||
pressed_stylus_button: None,
|
||||
confirm_delete_page: false,
|
||||
restorable_page: None,
|
||||
shutdown_error: None,
|
||||
@@ -1635,6 +1642,70 @@ fn pattern_color_button(
|
||||
/// Schieberegler für die Druckkurve mit Vorschau der Kennlinie. Änderungen gelten sofort
|
||||
/// (`state.pressure_curve`); zurückgegeben wird die Kurve, sobald sie gespeichert werden soll
|
||||
/// (Regler losgelassen oder per Tastatur geändert).
|
||||
/// Belegung der Tasten am Stift. Tasten erscheinen, sobald sie einmal gedrückt wurden; die
|
||||
/// gerade gedrückte ist markiert. `true`, wenn sich etwas geändert hat.
|
||||
fn stylus_buttons_editor(ui: &mut egui::Ui, state: &mut UiState) -> bool {
|
||||
use crate::settings::StylusAction;
|
||||
let mut changed = false;
|
||||
ui.add_space(4.0);
|
||||
ui.label(egui::RichText::new("Tasten am Stift").color(egui::Color32::from_gray(200)));
|
||||
if state.stylus_buttons.is_empty() {
|
||||
ui.label(
|
||||
egui::RichText::new(
|
||||
"Drücke eine Taste am Stift (über dem Bildschirm) – sie erscheint dann hier. \
|
||||
Bis dahin radieren alle Tasten.",
|
||||
)
|
||||
.size(11.0)
|
||||
.color(egui::Color32::from_gray(150)),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
let mut remove = None;
|
||||
for i in 0..state.stylus_buttons.len() {
|
||||
ui.horizontal(|ui| {
|
||||
let pressed = state.pressed_stylus_button == Some(i);
|
||||
let label = egui::RichText::new(format!("Taste {}", i + 1));
|
||||
ui.label(if pressed {
|
||||
label.strong().color(egui::Color32::from_rgb(46, 204, 113))
|
||||
} else {
|
||||
label
|
||||
})
|
||||
.on_hover_text("Leuchtet grün, solange die Taste gedrückt ist");
|
||||
let current = state.stylus_buttons[i].action;
|
||||
egui::ComboBox::from_id_source(("stylus_button", i))
|
||||
.selected_text(current.label())
|
||||
.width(130.0)
|
||||
.show_ui(ui, |ui| {
|
||||
for action in StylusAction::ALL {
|
||||
if ui
|
||||
.selectable_label(action == current, action.label())
|
||||
.clicked()
|
||||
&& action != current
|
||||
{
|
||||
state.stylus_buttons[i].action = action;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if ui
|
||||
.small_button("🗙")
|
||||
.on_hover_text(
|
||||
"Vergessen (erscheint beim nächsten Drücken wieder, dann mit Radieren)",
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
remove = Some(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(i) = remove {
|
||||
state.stylus_buttons.remove(i);
|
||||
state.pressed_stylus_button = None;
|
||||
changed = true;
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn pressure_curve_editor(ui: &mut egui::Ui, state: &mut UiState) -> Option<PressureCurve> {
|
||||
let mut save = false;
|
||||
let mut track = |response: egui::Response| {
|
||||
@@ -3560,6 +3631,9 @@ pub fn render_ui(
|
||||
if let Some(curve) = pressure_curve_editor(ui, state) {
|
||||
actions.push(UiAction::SavePressureCurve(curve));
|
||||
}
|
||||
if stylus_buttons_editor(ui, state) {
|
||||
actions.push(UiAction::SaveStylusButtons);
|
||||
}
|
||||
|
||||
section(ui, "WERKZEUGLEISTE");
|
||||
ui.horizontal(|ui| {
|
||||
@@ -5295,6 +5369,43 @@ mod tests {
|
||||
actions
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stylus_buttons_in_menu() {
|
||||
use crate::settings::{StylusAction, StylusButton};
|
||||
let notebook = Notebook::new();
|
||||
let mut state = UiState::new();
|
||||
state.is_more_menu_open = true;
|
||||
// Ohne gelernte Tasten: nur der Hinweis, keine Aktion
|
||||
let ctx = egui::Context::default();
|
||||
let updater = crate::updater::UpdaterController::new();
|
||||
let shapes = ctx
|
||||
.run(egui::RawInput::default(), |ctx| {
|
||||
let _ = render_ui(ctx, &mut state, ¬ebook, AutosaveStatus::Saved, &updater);
|
||||
})
|
||||
.shapes
|
||||
.len();
|
||||
assert!(shapes > 0);
|
||||
|
||||
// Mit zwei Tasten: beide Zeilen, die gedrückte hervorgehoben, ohne Fehler
|
||||
state.stylus_buttons = vec![
|
||||
StylusButton {
|
||||
id: 1,
|
||||
action: StylusAction::Erase,
|
||||
},
|
||||
StylusButton {
|
||||
id: 2,
|
||||
action: StylusAction::Select,
|
||||
},
|
||||
];
|
||||
state.pressed_stylus_button = Some(1);
|
||||
let output = ctx.run(egui::RawInput::default(), |ctx| {
|
||||
let actions = render_ui(ctx, &mut state, ¬ebook, AutosaveStatus::Saved, &updater);
|
||||
assert!(!actions.contains(&UiAction::SaveStylusButtons));
|
||||
});
|
||||
assert!(!output.shapes.is_empty());
|
||||
assert_eq!(state.stylus_buttons.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_press_detection() {
|
||||
let t0 = Instant::now();
|
||||
|
||||
Reference in New Issue
Block a user