Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb190cf79 | ||
|
|
1148851585 | ||
|
|
eef4cc6b2b | ||
|
|
dc067af31e |
Generated
+4
-1
@@ -1393,7 +1393,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ink-notes"
|
||||
version = "0.21.0"
|
||||
version = "0.22.1"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"arboard",
|
||||
@@ -1418,6 +1418,9 @@ dependencies = [
|
||||
"tempfile",
|
||||
"ureq",
|
||||
"url",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"webbrowser",
|
||||
"wgpu",
|
||||
"winit",
|
||||
|
||||
+6
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ink-notes"
|
||||
version = "0.21.0"
|
||||
version = "0.22.1"
|
||||
edition = "2021"
|
||||
description = "Notizbuch-App für Stift, Touch und Maus unter Linux"
|
||||
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
||||
@@ -51,6 +51,11 @@ hayro-svg = "0.8"
|
||||
# Text-Werkzeug: Schriftmetriken und Rasterung (Schriften aus egui)
|
||||
ab_glyph = "0.2"
|
||||
|
||||
# Bildschirmtastatur unter Wayland anfordern (eigenes text-input-Objekt, siehe src/osk.rs)
|
||||
wayland-client = "0.31"
|
||||
wayland-backend = { version = "0.3", features = ["client_system"] }
|
||||
wayland-protocols = { version = "0.31", features = ["client", "unstable"] }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1 # Erhöht Rendering-Performance auch im Debug-Modus
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Terminal=false
|
||||
Type=Application
|
||||
Categories=Office;Graphics;Utility;
|
||||
Keywords=Note;Notiz;Pen;Stift;Ink;Inking;Stylus;Drawing;Zeichnen;PDF;Vector;Text;Annotation;
|
||||
StartupWMClass=ink-notes
|
||||
StartupWMClass=eu.pansi.ink-notes
|
||||
MimeType=application/x-inknotes;
|
||||
Actions=NewWindow;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Terminal=false
|
||||
Type=Application
|
||||
Categories=Office;Graphics;Utility;
|
||||
Keywords=Note;Notiz;Pen;Stift;Ink;Inking;Stylus;Drawing;Zeichnen;PDF;Vector;Text;Annotation;
|
||||
StartupWMClass=ink-notes
|
||||
StartupWMClass=eu.pansi.ink-notes
|
||||
MimeType=application/x-inknotes;
|
||||
Actions=NewWindow;
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Notizbuch-App für Stift, Touch und Maus unter Linux (getestet mit Zorin OS 18 / Ubuntu 24.04).
|
||||
|
||||
### Highlights:
|
||||
- **App-Symbol**: Das Fenster meldet sich als eu.pansi.ink-notes – Dock und Taskleiste zeigen das ink-notes-Symbol statt eines allgemeinen Symbols (unter X11 auch als Fenstersymbol)
|
||||
- **Platz schaffen**: Neues Werkzeug ↕ – aufs Blatt tippen und nach unten ziehen, alles darunter rutscht mit, auch auf den folgenden Seiten (über das Seitenende hinaus geht es auf der nächsten Seite weiter, fehlende Seiten werden angehängt); nach oben ziehen schließt leeren Platz; ein Schritt für Rückgängig
|
||||
- **Achsenkreuz**: Ein „+“ in einem Zug (mit Halten oder dem Formen-Werkzeug) wird zum Achsenkreuz mit Pfeilspitzen an allen vier Enden; auch im PDF-/Bildexport
|
||||
- **Fehlerbehebung Stift**: Das erste Antippen mit dem Stift trifft jetzt – Knöpfe und Notizbücher reagieren sofort statt erst beim zweiten Tippen; Striche beginnen nicht mehr mit einer Linie von der vorherigen Stiftposition
|
||||
- **Tasten am Stift belegen**: Im ☰-Menü unter Eingabe jede Stifttaste mit Radieren, Auswählen, Textmarker, Stift oder Nichts belegen (gilt, solange die Taste gedrückt ist); Tasten erscheinen nach dem ersten Drücken und leuchten beim Drücken grün
|
||||
|
||||
@@ -12,6 +12,7 @@ pub mod geometry;
|
||||
pub mod history;
|
||||
pub mod image_layer;
|
||||
pub mod library;
|
||||
pub mod osk;
|
||||
pub mod page_export;
|
||||
pub mod pdf_doc;
|
||||
pub mod pdf_merge;
|
||||
|
||||
+81
-8
@@ -12,6 +12,7 @@ use winit::{
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
keyboard::{Key, ModifiersState, NamedKey},
|
||||
platform::{wayland::EventLoopBuilderExtWayland, x11::EventLoopBuilderExtX11},
|
||||
raw_window_handle::{HasDisplayHandle, RawDisplayHandle},
|
||||
window::{Window, WindowBuilder},
|
||||
};
|
||||
|
||||
@@ -25,6 +26,7 @@ use ink_notes::export::{self, export_notebook_to_pdf, ExportStrategy};
|
||||
use ink_notes::geometry::{self, RecognizedShape};
|
||||
use ink_notes::image_layer::TextureSource;
|
||||
use ink_notes::library::Library;
|
||||
use ink_notes::osk::{self, KeyboardRequester};
|
||||
use ink_notes::pdf_doc::PdfRenderService;
|
||||
use ink_notes::platform::{self, DisplayBackend};
|
||||
use ink_notes::selection::{
|
||||
@@ -37,6 +39,23 @@ use ink_notes::storage::{self, NotebookManifest};
|
||||
use ink_notes::ui::{self, ActiveTool, UiAction, UiState};
|
||||
use ink_notes::updater::UpdaterController;
|
||||
|
||||
/// Kennung der App (Name der .desktop-Datei und des Symbols).
|
||||
const APP_ID: &str = "eu.pansi.ink-notes";
|
||||
|
||||
/// Fenstersymbol aus dem eingebauten App-Symbol, auf 64 × 64 Pixel verkleinert: In voller
|
||||
/// Größe (256 × 256) überschreitet es die Größe einer einzelnen X11-Anfrage und fehlt dann.
|
||||
fn app_icon() -> Option<winit::window::Icon> {
|
||||
const SIZE: u32 = 64;
|
||||
let image = image::load_from_memory(include_bytes!("../assets/ink-notes.png")).ok()?;
|
||||
let small = image::imageops::resize(
|
||||
&image.into_rgba8(),
|
||||
SIZE,
|
||||
SIZE,
|
||||
image::imageops::FilterType::Triangle,
|
||||
);
|
||||
winit::window::Icon::from_rgba(small.into_raw(), SIZE, SIZE).ok()
|
||||
}
|
||||
|
||||
/// Platz schaffen: Druck auf das Blatt (physische Pixel) setzt die Ansatzhöhe auf der
|
||||
/// aktuellen Seite. `false` neben dem Blatt oder auf einer PDF-Seite.
|
||||
fn space_press(
|
||||
@@ -1099,12 +1118,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
DisplayBackend::Automatic => {}
|
||||
}
|
||||
let event_loop = event_loop_builder.build()?;
|
||||
let window = Arc::new(
|
||||
WindowBuilder::new()
|
||||
.with_title("ink-notes (Latenzoptimiertes Linux-Inking)")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(1280.0, 850.0))
|
||||
.build(&event_loop)?,
|
||||
);
|
||||
// App-Kennung wie die .desktop-Datei (eu.pansi.ink-notes): Daran ordnen Dock und Taskleiste
|
||||
// das Fenster dem App-Symbol zu (Wayland: app_id, X11: WM_CLASS). Das Fenstersymbol selbst
|
||||
// nutzt nur X11.
|
||||
let builder = WindowBuilder::new()
|
||||
.with_title("ink-notes")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(1280.0, 850.0))
|
||||
.with_window_icon(app_icon());
|
||||
let builder =
|
||||
winit::platform::wayland::WindowBuilderExtWayland::with_name(builder, APP_ID, "ink-notes");
|
||||
let builder =
|
||||
winit::platform::x11::WindowBuilderExtX11::with_name(builder, APP_ID, "ink-notes");
|
||||
let window = Arc::new(builder.build(&event_loop)?);
|
||||
|
||||
let mut canvas = match pollster::block_on(CanvasState::new(window.clone())) {
|
||||
Ok(canvas) => canvas,
|
||||
@@ -1125,6 +1150,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
};
|
||||
|
||||
// Bildschirmtastatur (nur Wayland): eigenes text-input-Objekt, siehe osk.rs
|
||||
let mut keyboard_requester = match window.display_handle().map(|handle| handle.as_raw()) {
|
||||
// Safety: `window` lebt bis zum Ende von main, die Event-Schleife besitzt beide
|
||||
Ok(RawDisplayHandle::Wayland(handle)) => {
|
||||
Some(unsafe { KeyboardRequester::new(handle.display.as_ptr()) })
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let mut ime_was_active = false;
|
||||
let mut ime_composing = false;
|
||||
|
||||
// 1. Notizbuch aus Storage laden oder initialisieren (Session-Wiederherstellung)
|
||||
let storage_base_dir = storage::default_storage_dir();
|
||||
let _ = storage::cleanup_trash(
|
||||
@@ -1284,6 +1320,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut pinch_prev_distance: Option<f32> = None;
|
||||
// Berührungen, die auf der UI begonnen haben, gehören bis zum Abheben egui
|
||||
let mut ui_touch_ids: std::collections::HashSet<u64> = std::collections::HashSet::new();
|
||||
// Alle aufliegenden Finger (auch auf der UI), unabhängig von der Gestenauswertung
|
||||
let mut touches_down: std::collections::HashSet<u64> = std::collections::HashSet::new();
|
||||
let mut pinch_prev_center: Option<(f32, f32)> = None;
|
||||
let mut is_pinch_gesturing = false;
|
||||
let mut is_touch_drawing = false;
|
||||
@@ -1846,7 +1884,30 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
Event::WindowEvent { event, window_id } if window_id == window.id() => {
|
||||
// 1. Window-Event zuerst an egui übergeben
|
||||
let egui_response = egui_winit_state.on_window_event(&window, &event);
|
||||
if let WindowEvent::Touch(touch) = &event {
|
||||
match touch.phase {
|
||||
TouchPhase::Started => {
|
||||
touches_down.insert(touch.id);
|
||||
}
|
||||
TouchPhase::Ended | TouchPhase::Cancelled => {
|
||||
touches_down.remove(&touch.id);
|
||||
}
|
||||
TouchPhase::Moved => {}
|
||||
}
|
||||
}
|
||||
// Beim Antippen blendet GNOME den Mauszeiger aus und meldet CursorLeft, während der
|
||||
// Finger noch aufliegt. egui-winit verwirft dabei die Zeigerposition und meldet das
|
||||
// Abheben nicht mehr, der Tipp ginge verloren – daher erfährt egui davon nichts.
|
||||
let egui_response = if matches!(event, WindowEvent::CursorLeft { .. })
|
||||
&& !touches_down.is_empty()
|
||||
{
|
||||
egui_winit::EventResponse {
|
||||
repaint: true,
|
||||
consumed: false,
|
||||
}
|
||||
} else {
|
||||
egui_winit_state.on_window_event(&window, &event)
|
||||
};
|
||||
if egui_response.repaint {
|
||||
window.request_redraw();
|
||||
}
|
||||
@@ -2915,7 +2976,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
// 1. egui Eingabe erfassen und Frame starten
|
||||
let raw_input = egui_winit_state.take_egui_input(&window);
|
||||
let mut raw_input = egui_winit_state.take_egui_input(&window);
|
||||
osk::plain_ime_commits_to_text(&mut raw_input.events, &mut ime_composing);
|
||||
if let Some(requester) = keyboard_requester.as_mut() {
|
||||
raw_input.events.extend(requester.take_deletions());
|
||||
}
|
||||
egui_ctx.begin_frame(raw_input);
|
||||
|
||||
// 2. Auswahl-Overlay rendern (Bounding Box, Griffe, Lasso)
|
||||
@@ -3679,7 +3744,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// 4. egui Frame beenden & Primitives tessellieren
|
||||
let full_output = egui_ctx.end_frame();
|
||||
let ime_active = full_output.platform_output.ime.is_some();
|
||||
egui_winit_state.handle_platform_output(&window, full_output.platform_output);
|
||||
if let Some(requester) = keyboard_requester.as_mut() {
|
||||
let pressed = egui_ctx.input(|i| i.pointer.any_pressed());
|
||||
if osk::should_request(ime_was_active, ime_active, pressed) {
|
||||
requester.request();
|
||||
}
|
||||
}
|
||||
ime_was_active = ime_active;
|
||||
|
||||
// egui braucht Folge-Frames, z. B. um neu angeordnete Bereiche zu vermessen
|
||||
// (die Werkzeugleiste wird erst im 2.–3. Frame korrekt zentriert)
|
||||
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
//! Bildschirmtastatur unter Wayland anfordern.
|
||||
//!
|
||||
//! GNOME (mutter) blendet die Bildschirmtastatur nicht schon beim ersten `enable` eines
|
||||
//! Texteingabefelds (`zwp_text_input_v3`) ein, sondern erst, wenn ein bereits aktives Feld
|
||||
//! `enable` erneut meldet – so macht es GTK beim Antippen. winit meldet `enable` nur einmal,
|
||||
//! sobald egui ein Textfeld fokussiert. Daher schickt ink-notes über ein eigenes
|
||||
//! text-input-Objekt auf derselben Wayland-Verbindung ein weiteres `enable`. Die getippten
|
||||
//! Zeichen kommen weiterhin über winit an.
|
||||
//!
|
||||
//! Die Rücktaste der GNOME-Bildschirmtastatur löscht über `delete_surrounding_text` im zuvor
|
||||
//! gemeldeten Umgebungstext; ohne Umgebungstext passiert nichts, und winit wertet das Löschen
|
||||
//! ohnehin nicht aus. Daher meldet das eigene Objekt einen Platzhalter als Umgebungstext und
|
||||
//! übersetzt die Löschwünsche in Tastendrücke (Rücktaste/Entf) für egui.
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use wayland_client::{
|
||||
delegate_noop,
|
||||
protocol::{wl_registry, wl_seat},
|
||||
Connection, Dispatch, EventQueue, QueueHandle,
|
||||
};
|
||||
use wayland_protocols::wp::text_input::zv3::client::{
|
||||
zwp_text_input_manager_v3::ZwpTextInputManagerV3,
|
||||
zwp_text_input_v3::{self, ZwpTextInputV3},
|
||||
};
|
||||
|
||||
/// Platzhalter-Umgebungstext: ein Zeichen vor der Schreibmarke, damit die Rücktaste der
|
||||
/// Bildschirmtastatur immer etwas zu löschen hat (ein Byte = ein Zeichen)
|
||||
const SURROUNDING_PLACEHOLDER: &str = " ";
|
||||
|
||||
/// Wandelt Texteingaben ohne Vorschau (Preedit) in normale Texteingaben um.
|
||||
///
|
||||
/// Die Bildschirmtastatur schickt jedes Zeichen als fertigen IME-Text, ohne vorher eine Vorschau
|
||||
/// zu melden. egui 0.27 fügt solche Texte nur ein, solange die Schreibmarke dort steht, wo die
|
||||
/// letzte Vorschau begann – also nur das erste Zeichen. `composing` merkt sich über Frames hinweg,
|
||||
/// ob gerade eine Vorschau läuft; deren Abschluss bleibt bei egui.
|
||||
pub fn plain_ime_commits_to_text(events: &mut [egui::Event], composing: &mut bool) {
|
||||
for event in events {
|
||||
match event {
|
||||
egui::Event::CompositionStart => *composing = true,
|
||||
egui::Event::CompositionEnd(text) => {
|
||||
if !*composing && !text.is_empty() && !text.contains(['\n', '\r']) {
|
||||
*event = egui::Event::Text(std::mem::take(text));
|
||||
}
|
||||
*composing = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tastendrücke für egui, die `back` Zeichen vor und `forward` Zeichen nach der Schreibmarke
|
||||
/// löschen
|
||||
pub fn deletion_key_events(back: u32, forward: u32) -> Vec<egui::Event> {
|
||||
let presses = std::iter::repeat_n(egui::Key::Backspace, back as usize)
|
||||
.chain(std::iter::repeat_n(egui::Key::Delete, forward as usize));
|
||||
presses
|
||||
.flat_map(|key| {
|
||||
[true, false].map(|pressed| egui::Event::Key {
|
||||
key,
|
||||
physical_key: None,
|
||||
pressed,
|
||||
repeat: false,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Entscheidet, ob die Bildschirmtastatur angefordert wird: sobald ein Textfeld den Fokus
|
||||
/// bekommt und erneut bei jedem Antippen des fokussierten Felds (falls die Tastatur inzwischen
|
||||
/// zugeklappt wurde).
|
||||
pub fn should_request(was_active: bool, active: bool, pressed: bool) -> bool {
|
||||
active && (!was_active || pressed)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
seat: Option<wl_seat::WlSeat>,
|
||||
manager: Option<ZwpTextInputManagerV3>,
|
||||
text_input: Option<ZwpTextInputV3>,
|
||||
/// Von der Bildschirmtastatur gewünschtes Löschen (Zeichen vor/nach der Schreibmarke)
|
||||
delete_back: u32,
|
||||
delete_forward: u32,
|
||||
}
|
||||
|
||||
/// Eigenes text-input-Objekt, mit dem ink-notes die Bildschirmtastatur anfordert
|
||||
pub struct KeyboardRequester {
|
||||
conn: Connection,
|
||||
queue: EventQueue<State>,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl KeyboardRequester {
|
||||
/// Hängt sich an die Wayland-Verbindung des Fensters.
|
||||
///
|
||||
/// # Safety
|
||||
/// `wl_display` muss auf die `wl_display` des Fensters zeigen und gültig bleiben, solange
|
||||
/// der `KeyboardRequester` lebt.
|
||||
pub unsafe fn new(wl_display: *mut c_void) -> Self {
|
||||
// Safety: an den Aufrufer weitergereicht
|
||||
let backend =
|
||||
unsafe { wayland_backend::client::Backend::from_foreign_display(wl_display.cast()) };
|
||||
let conn = Connection::from_backend(backend);
|
||||
let queue = conn.new_event_queue();
|
||||
conn.display().get_registry(&queue.handle(), ());
|
||||
let _ = conn.flush();
|
||||
Self {
|
||||
conn,
|
||||
queue,
|
||||
state: State::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verarbeitet eingetroffene Ereignisse; einmal pro Frame aufrufen.
|
||||
pub fn pump(&mut self) {
|
||||
let _ = self.queue.dispatch_pending(&mut self.state);
|
||||
if self.state.text_input.is_none() {
|
||||
if let (Some(manager), Some(seat)) = (&self.state.manager, &self.state.seat) {
|
||||
self.state.text_input =
|
||||
Some(manager.get_text_input(seat, &self.queue.handle(), ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bittet den Compositor, die Bildschirmtastatur einzublenden. Muss nach dem `enable` von
|
||||
/// winit kommen (also nach `handle_platform_output`).
|
||||
pub fn request(&mut self) {
|
||||
self.pump();
|
||||
if let Some(text_input) = &self.state.text_input {
|
||||
text_input.enable();
|
||||
self.send_surrounding_placeholder();
|
||||
}
|
||||
}
|
||||
|
||||
/// Tastendrücke für das seit dem letzten Aufruf gewünschte Löschen (vor `begin_frame`
|
||||
/// aufrufen und an die egui-Eingabe anhängen)
|
||||
pub fn take_deletions(&mut self) -> Vec<egui::Event> {
|
||||
self.pump();
|
||||
// Den Platzhalter hier nicht neu melden: Die Tastatur löscht bei jeder neuen Meldung
|
||||
// erneut, solange die Rücktaste gedrückt ist – ein kurzes Antippen löschte sonst doppelt.
|
||||
let back = std::mem::take(&mut self.state.delete_back);
|
||||
let forward = std::mem::take(&mut self.state.delete_forward);
|
||||
deletion_key_events(back, forward)
|
||||
}
|
||||
|
||||
fn send_surrounding_placeholder(&self) {
|
||||
if let Some(text_input) = &self.state.text_input {
|
||||
let len = SURROUNDING_PLACEHOLDER.len() as i32;
|
||||
text_input.set_surrounding_text(SURROUNDING_PLACEHOLDER.to_owned(), len, len);
|
||||
text_input.commit();
|
||||
let _ = self.conn.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_registry::Event::Global {
|
||||
name, interface, ..
|
||||
} = event
|
||||
{
|
||||
match interface.as_str() {
|
||||
"wl_seat" if state.seat.is_none() => {
|
||||
state.seat = Some(registry.bind(name, 1, qh, ()));
|
||||
}
|
||||
"zwp_text_input_manager_v3" if state.manager.is_none() => {
|
||||
state.manager = Some(registry.bind(name, 1, qh, ()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_noop!(State: ignore wl_seat::WlSeat);
|
||||
delegate_noop!(State: ZwpTextInputManagerV3);
|
||||
impl Dispatch<ZwpTextInputV3, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &ZwpTextInputV3,
|
||||
event: zwp_text_input_v3::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
// Getippter Text kommt über winit; hier nur das Löschen
|
||||
if let zwp_text_input_v3::Event::DeleteSurroundingText {
|
||||
before_length,
|
||||
after_length,
|
||||
} = event
|
||||
{
|
||||
state.delete_back += before_length;
|
||||
state.delete_forward += after_length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn requests_when_text_field_gets_focus() {
|
||||
assert!(should_request(false, true, false));
|
||||
assert!(should_request(false, true, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_again_when_focused_field_is_tapped() {
|
||||
assert!(should_request(true, true, true));
|
||||
assert!(!should_request(true, true, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commits_without_preedit_become_text() {
|
||||
let mut composing = false;
|
||||
let mut events = vec![
|
||||
egui::Event::CompositionEnd("h".into()),
|
||||
egui::Event::CompositionEnd("i".into()),
|
||||
];
|
||||
plain_ime_commits_to_text(&mut events, &mut composing);
|
||||
assert_eq!(
|
||||
events,
|
||||
vec![egui::Event::Text("h".into()), egui::Event::Text("i".into())]
|
||||
);
|
||||
assert!(!composing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commits_after_preedit_stay_with_egui() {
|
||||
let mut composing = false;
|
||||
let mut events = vec![
|
||||
egui::Event::CompositionStart,
|
||||
egui::Event::CompositionUpdate("ni".into()),
|
||||
];
|
||||
plain_ime_commits_to_text(&mut events, &mut composing);
|
||||
assert!(composing);
|
||||
// Abschluss erst im nächsten Frame
|
||||
let mut events = vec![egui::Event::CompositionEnd("你".into())];
|
||||
plain_ime_commits_to_text(&mut events, &mut composing);
|
||||
assert_eq!(events, vec![egui::Event::CompositionEnd("你".into())]);
|
||||
assert!(!composing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_breaks_and_empty_commits_are_left_alone() {
|
||||
let mut composing = false;
|
||||
let mut events = vec![
|
||||
egui::Event::CompositionEnd("\n".into()),
|
||||
egui::Event::CompositionEnd(String::new()),
|
||||
];
|
||||
let before = events.clone();
|
||||
plain_ime_commits_to_text(&mut events, &mut composing);
|
||||
assert_eq!(events, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletions_become_key_presses() {
|
||||
let events = deletion_key_events(2, 1);
|
||||
let keys: Vec<_> = events
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
egui::Event::Key { key, pressed, .. } => (*key, *pressed),
|
||||
other => panic!("unerwartet: {other:?}"),
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
keys,
|
||||
vec![
|
||||
(egui::Key::Backspace, true),
|
||||
(egui::Key::Backspace, false),
|
||||
(egui::Key::Backspace, true),
|
||||
(egui::Key::Backspace, false),
|
||||
(egui::Key::Delete, true),
|
||||
(egui::Key::Delete, false),
|
||||
]
|
||||
);
|
||||
assert!(deletion_key_events(0, 0).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_request_without_focused_text_field() {
|
||||
assert!(!should_request(false, false, true));
|
||||
assert!(!should_request(true, false, true));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user