2023-05-18 03:53:38 +03:00
|
|
|
|
// Copyright 2023 The Grim Developers
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
2023-07-16 11:23:56 +03:00
|
|
|
|
use std::sync::atomic::{AtomicI32, Ordering};
|
2024-05-15 14:47:19 +03:00
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use parking_lot::RwLock;
|
2024-04-16 15:24:22 +03:00
|
|
|
|
use lazy_static::lazy_static;
|
2023-07-16 11:23:56 +03:00
|
|
|
|
|
2024-05-21 13:31:46 +03:00
|
|
|
|
use egui::{Align, Button, CursorIcon, Layout, lerp, PointerState, Rect, Response, Rgba, RichText, Sense, Spinner, TextBuffer, TextStyle, Widget};
|
2024-04-17 01:30:28 +03:00
|
|
|
|
use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke};
|
2023-06-02 02:05:34 +03:00
|
|
|
|
use egui::epaint::text::TextWrapping;
|
2024-04-16 15:24:22 +03:00
|
|
|
|
use egui::os::OperatingSystem;
|
2023-06-03 11:22:51 +03:00
|
|
|
|
use egui::text::{LayoutJob, TextFormat};
|
2024-04-16 15:24:22 +03:00
|
|
|
|
use egui::text_edit::TextEditState;
|
2024-05-22 13:23:05 +03:00
|
|
|
|
use crate::AppConfig;
|
2023-05-18 03:53:38 +03:00
|
|
|
|
|
2023-07-03 21:17:49 +03:00
|
|
|
|
use crate::gui::Colors;
|
2024-04-30 18:15:03 +03:00
|
|
|
|
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN, SQUARE};
|
2023-11-08 01:00:56 +03:00
|
|
|
|
use crate::gui::platform::PlatformCallbacks;
|
|
|
|
|
use crate::gui::views::types::TextEditOptions;
|
2023-05-18 03:53:38 +03:00
|
|
|
|
|
|
|
|
|
pub struct View;
|
|
|
|
|
|
|
|
|
|
impl View {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
/// Get default stroke around views.
|
|
|
|
|
pub fn default_stroke() -> Stroke {
|
|
|
|
|
Stroke { width: 1.0, color: Colors::stroke() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get default stroke around item buttons.
|
|
|
|
|
pub fn item_stroke() -> Stroke {
|
|
|
|
|
Stroke { width: 1.0, color: Colors::item_stroke() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get stroke for hovered items and buttons.
|
|
|
|
|
pub fn hover_stroke() -> Stroke {
|
|
|
|
|
Stroke { width: 1.0, color: Colors::item_hover() }
|
|
|
|
|
}
|
2023-05-18 03:53:38 +03:00
|
|
|
|
|
2023-08-15 21:20:20 +03:00
|
|
|
|
/// Draw content with maximum width value.
|
|
|
|
|
pub fn max_width_ui(ui: &mut egui::Ui,
|
|
|
|
|
max_width: f32,
|
|
|
|
|
add_content: impl FnOnce(&mut egui::Ui)) {
|
|
|
|
|
// Setup content width.
|
|
|
|
|
let mut width = ui.available_width();
|
|
|
|
|
if width == 0.0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let mut rect = ui.available_rect_before_wrap();
|
|
|
|
|
width = f32::min(width, max_width);
|
|
|
|
|
rect.set_width(width);
|
|
|
|
|
|
|
|
|
|
// Draw content.
|
|
|
|
|
ui.allocate_ui(rect.size(), |ui| {
|
|
|
|
|
(add_content)(ui);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-16 15:24:22 +03:00
|
|
|
|
/// Get width and height of app window.
|
|
|
|
|
pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) {
|
|
|
|
|
ui.ctx().input(|i| {
|
|
|
|
|
return match i.viewport().inner_rect {
|
|
|
|
|
None => {
|
2024-05-22 13:23:05 +03:00
|
|
|
|
if let Some(size) = i.viewport().monitor_size {
|
|
|
|
|
(size.x, size.y)
|
|
|
|
|
} else {
|
|
|
|
|
(AppConfig::DEFAULT_WIDTH, AppConfig::DEFAULT_HEIGHT)
|
|
|
|
|
}
|
2024-04-16 15:24:22 +03:00
|
|
|
|
}
|
|
|
|
|
Some(rect) => {
|
|
|
|
|
(rect.width(), rect.height())
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-23 17:21:02 +03:00
|
|
|
|
/// Callback on Enter key press event.
|
|
|
|
|
pub fn on_enter_key(ui: &mut egui::Ui, cb: impl FnOnce()) {
|
|
|
|
|
if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter)) {
|
|
|
|
|
(cb)();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-16 11:23:56 +03:00
|
|
|
|
/// Calculate margin for far left view based on display insets (cutouts).
|
|
|
|
|
pub fn far_left_inset_margin(ui: &mut egui::Ui) -> f32 {
|
|
|
|
|
if ui.available_rect_before_wrap().min.x == 0.0 {
|
|
|
|
|
Self::get_left_inset()
|
|
|
|
|
} else {
|
|
|
|
|
0.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Calculate margin for far left view based on display insets (cutouts).
|
2024-04-16 15:24:22 +03:00
|
|
|
|
pub fn far_right_inset_margin(ui: &mut egui::Ui) -> f32 {
|
2023-07-16 11:23:56 +03:00
|
|
|
|
let container_width = ui.available_rect_before_wrap().max.x as i32;
|
2024-04-16 15:24:22 +03:00
|
|
|
|
let window_size = Self::window_size(ui);
|
|
|
|
|
let display_width = window_size.0 as i32;
|
2023-07-21 03:55:13 +03:00
|
|
|
|
// Means end of the screen.
|
2023-07-16 11:23:56 +03:00
|
|
|
|
if container_width == display_width {
|
|
|
|
|
Self::get_right_inset()
|
|
|
|
|
} else {
|
|
|
|
|
0.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-13 03:54:27 +03:00
|
|
|
|
/// Cut long text with ﹍ character.
|
|
|
|
|
fn ellipsize(text: String, size: f32, color: Color32) -> LayoutJob {
|
2023-06-02 02:05:34 +03:00
|
|
|
|
let mut job = LayoutJob::single_section(text, TextFormat {
|
2023-06-27 02:11:07 +03:00
|
|
|
|
font_id: FontId::proportional(size), color, ..Default::default()
|
2023-06-02 02:05:34 +03:00
|
|
|
|
});
|
|
|
|
|
job.wrap = TextWrapping {
|
|
|
|
|
max_rows: 1,
|
2023-08-09 02:51:50 +03:00
|
|
|
|
break_anywhere: true,
|
2023-06-02 02:05:34 +03:00
|
|
|
|
overflow_character: Option::from('﹍'),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
2023-07-13 03:54:27 +03:00
|
|
|
|
job
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 21:37:29 +03:00
|
|
|
|
/// Draw ellipsized text.
|
2023-07-13 03:54:27 +03:00
|
|
|
|
pub fn ellipsize_text(ui: &mut egui::Ui, text: String, size: f32, color: Color32) {
|
|
|
|
|
ui.label(Self::ellipsize(text, size, color));
|
2023-06-02 02:05:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-17 21:37:29 +03:00
|
|
|
|
/// Draw animated ellipsized text.
|
|
|
|
|
pub fn animate_text(ui: &mut egui::Ui, text: String, size: f32, color: Color32, animate: bool) {
|
|
|
|
|
// Setup text color animation if needed.
|
|
|
|
|
let (dark, bright) = (0.3, 1.0);
|
|
|
|
|
let color_factor = if animate {
|
|
|
|
|
lerp(dark..=bright, ui.input(|i| i.time).cos().abs()) as f32
|
|
|
|
|
} else {
|
|
|
|
|
bright as f32
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Draw subtitle text.
|
|
|
|
|
let sub_color_rgba = Rgba::from(color) * color_factor;
|
|
|
|
|
let sub_color = Color32::from(sub_color_rgba);
|
|
|
|
|
View::ellipsize_text(ui, text, size, sub_color);
|
|
|
|
|
|
|
|
|
|
// Repaint delay based on animation status.
|
|
|
|
|
if animate {
|
|
|
|
|
ui.ctx().request_repaint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 00:38:25 +03:00
|
|
|
|
/// Draw horizontally centered sub-title with space below.
|
|
|
|
|
pub fn sub_title(ui: &mut egui::Ui, text: String) {
|
|
|
|
|
ui.vertical_centered_justified(|ui| {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.label(RichText::new(text.to_uppercase()).size(16.0).color(Colors::text(false)));
|
2023-06-20 00:38:25 +03:00
|
|
|
|
});
|
|
|
|
|
ui.add_space(4.0);
|
2023-06-02 02:05:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 02:13:47 +03:00
|
|
|
|
/// Temporary click optimization for touch screens, return `true` if it was clicked.
|
|
|
|
|
fn touched(ui: &mut egui::Ui, resp: Response) -> bool {
|
2023-06-20 00:38:25 +03:00
|
|
|
|
let drag_resp = resp.interact(Sense::click_and_drag());
|
2023-06-02 02:05:34 +03:00
|
|
|
|
// Clear pointer event if dragging is out of button area
|
2023-06-20 00:38:25 +03:00
|
|
|
|
if drag_resp.dragged() && !ui.rect_contains_pointer(drag_resp.rect) {
|
2023-07-07 03:50:08 +03:00
|
|
|
|
ui.input_mut(|i| i.pointer = PointerState::default());
|
2023-06-02 02:05:34 +03:00
|
|
|
|
}
|
2024-04-19 04:20:25 +03:00
|
|
|
|
if drag_resp.drag_stopped() || drag_resp.clicked() || drag_resp.secondary_clicked() {
|
2023-06-29 23:52:30 +03:00
|
|
|
|
return true;
|
2023-06-21 02:13:47 +03:00
|
|
|
|
}
|
|
|
|
|
false
|
2023-06-02 02:05:34 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Title button with transparent background fill color, contains only icon.
|
2023-05-18 03:53:38 +03:00
|
|
|
|
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
|
2023-05-23 23:45:16 +03:00
|
|
|
|
ui.scope(|ui| {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
// Disable strokes.
|
2023-08-09 02:22:16 +03:00
|
|
|
|
ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE;
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.style_mut().visuals.widgets.hovered.bg_stroke = Stroke::NONE;
|
|
|
|
|
ui.style_mut().visuals.widgets.active.bg_stroke = Stroke::NONE;
|
2023-11-08 01:00:56 +03:00
|
|
|
|
ui.style_mut().visuals.widgets.active.rounding = Rounding::default();
|
2023-07-30 10:52:24 +03:00
|
|
|
|
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
|
|
|
|
|
2023-07-11 21:02:36 +03:00
|
|
|
|
// Setup text.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
let wt = RichText::new(icon.to_string()).size(22.0).color(Colors::title(true));
|
2023-07-11 21:02:36 +03:00
|
|
|
|
// Draw button.
|
2023-06-02 02:05:34 +03:00
|
|
|
|
let br = Button::new(wt)
|
2023-06-03 11:22:51 +03:00
|
|
|
|
.fill(Colors::TRANSPARENT)
|
2023-08-13 19:44:41 +03:00
|
|
|
|
.ui(ui)
|
|
|
|
|
.on_hover_cursor(CursorIcon::PointingHand);
|
2023-08-05 22:39:42 +03:00
|
|
|
|
br.surrender_focus();
|
2023-06-21 02:13:47 +03:00
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(action)();
|
|
|
|
|
}
|
2023-05-23 23:45:16 +03:00
|
|
|
|
});
|
2023-05-18 03:53:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-02 02:05:34 +03:00
|
|
|
|
/// Tab button with white background fill color, contains only icon.
|
|
|
|
|
pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, action: impl FnOnce()) {
|
2023-07-31 01:04:41 +03:00
|
|
|
|
ui.scope(|ui| {
|
|
|
|
|
let text_color = match active {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
true => Colors::title(false),
|
|
|
|
|
false => Colors::text(false)
|
2023-07-31 01:04:41 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut button = Button::new(RichText::new(icon).size(22.0).color(text_color));
|
|
|
|
|
|
|
|
|
|
if !active {
|
|
|
|
|
// Disable expansion on click/hover.
|
|
|
|
|
ui.style_mut().visuals.widgets.hovered.expansion = 0.0;
|
|
|
|
|
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
|
|
|
|
// Setup fill colors.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.visuals_mut().widgets.inactive.weak_bg_fill = Colors::white_or_black(false);
|
|
|
|
|
ui.visuals_mut().widgets.hovered.weak_bg_fill = Colors::button();
|
|
|
|
|
ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill();
|
2023-07-31 01:04:41 +03:00
|
|
|
|
// Setup stroke colors.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.visuals_mut().widgets.inactive.bg_stroke = Self::default_stroke();
|
|
|
|
|
ui.visuals_mut().widgets.hovered.bg_stroke = Self::hover_stroke();
|
|
|
|
|
ui.visuals_mut().widgets.active.bg_stroke = Self::item_stroke();
|
2023-07-31 01:04:41 +03:00
|
|
|
|
} else {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
button = button.fill(Colors::fill()).stroke(Stroke::NONE);
|
2023-07-31 01:04:41 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 19:44:41 +03:00
|
|
|
|
let br = button.ui(ui).on_hover_cursor(CursorIcon::PointingHand);
|
2023-08-05 22:39:42 +03:00
|
|
|
|
br.surrender_focus();
|
2023-07-31 01:04:41 +03:00
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(action)();
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-05-18 03:53:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-21 23:43:00 +03:00
|
|
|
|
/// Draw [`Button`] with specified background fill and text color.
|
|
|
|
|
fn button_resp(ui: &mut egui::Ui, text: String, text_color: Color32, bg: Color32) -> Response {
|
|
|
|
|
let button_text = Self::ellipsize(text.to_uppercase(), 17.0, text_color);
|
|
|
|
|
Button::new(button_text)
|
2024-05-29 22:47:17 +03:00
|
|
|
|
.stroke(Self::default_stroke())
|
2024-04-21 23:43:00 +03:00
|
|
|
|
.fill(bg)
|
2023-08-13 19:44:41 +03:00
|
|
|
|
.ui(ui)
|
2024-04-21 23:43:00 +03:00
|
|
|
|
.on_hover_cursor(CursorIcon::PointingHand)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draw [`Button`] with specified background fill color and default text color.
|
|
|
|
|
pub fn button(ui: &mut egui::Ui, text: String, fill: Color32, action: impl FnOnce()) {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
let br = Self::button_resp(ui, text, Colors::text_button(), fill);
|
2024-04-21 23:43:00 +03:00
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(action)();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draw [`Button`] with specified background fill color and text color.
|
|
|
|
|
pub fn colored_text_button(ui: &mut egui::Ui,
|
|
|
|
|
text: String,
|
|
|
|
|
text_color: Color32,
|
|
|
|
|
fill: Color32,
|
|
|
|
|
action: impl FnOnce()) {
|
|
|
|
|
let br = Self::button_resp(ui, text, text_color, fill);
|
2023-06-21 02:13:47 +03:00
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(action)();
|
|
|
|
|
}
|
2023-05-18 03:53:38 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-29 22:47:17 +03:00
|
|
|
|
/// Draw gold action [`Button`].
|
|
|
|
|
pub fn action_button(ui: &mut egui::Ui,
|
|
|
|
|
text: String, action: impl FnOnce()) {
|
|
|
|
|
Self::colored_text_button(ui, text, Colors::title(true), Colors::gold(), action);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-21 23:43:00 +03:00
|
|
|
|
/// Draw [`Button`] with specified background fill color and ui at callback.
|
|
|
|
|
pub fn button_ui(ui: &mut egui::Ui,
|
|
|
|
|
text: String,
|
|
|
|
|
fill: Color32,
|
|
|
|
|
action: impl FnOnce(&mut egui::Ui)) {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
let button_text = Self::ellipsize(text.to_uppercase(), 17.0, Colors::text_button());
|
2024-04-16 15:24:22 +03:00
|
|
|
|
let br = Button::new(button_text)
|
2024-05-29 22:47:17 +03:00
|
|
|
|
.stroke(Self::default_stroke())
|
2024-04-16 15:24:22 +03:00
|
|
|
|
.fill(fill)
|
|
|
|
|
.ui(ui)
|
|
|
|
|
.on_hover_cursor(CursorIcon::PointingHand);
|
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(action)(ui);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-09 02:22:16 +03:00
|
|
|
|
/// Draw list item [`Button`] with provided rounding.
|
|
|
|
|
pub fn item_button(ui: &mut egui::Ui,
|
|
|
|
|
rounding: Rounding,
|
|
|
|
|
text: &'static str,
|
|
|
|
|
color: Option<Color32>,
|
|
|
|
|
action: impl FnOnce()) {
|
2023-07-30 10:52:24 +03:00
|
|
|
|
// Setup button size.
|
|
|
|
|
let mut rect = ui.available_rect_before_wrap();
|
|
|
|
|
rect.set_width(32.0);
|
|
|
|
|
let button_size = rect.size();
|
|
|
|
|
|
|
|
|
|
ui.scope(|ui| {
|
2024-04-22 16:04:18 +03:00
|
|
|
|
// Setup padding for item buttons.
|
|
|
|
|
ui.style_mut().spacing.button_padding = egui::vec2(14.0, 0.0);
|
2023-07-30 10:52:24 +03:00
|
|
|
|
// Disable expansion on click/hover.
|
|
|
|
|
ui.style_mut().visuals.widgets.hovered.expansion = 0.0;
|
|
|
|
|
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
|
|
|
|
// Setup fill colors.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.visuals_mut().widgets.inactive.weak_bg_fill = Colors::white_or_black(false);
|
|
|
|
|
ui.visuals_mut().widgets.hovered.weak_bg_fill = Colors::button();
|
|
|
|
|
ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill();
|
2023-07-30 10:52:24 +03:00
|
|
|
|
// Setup stroke colors.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.visuals_mut().widgets.inactive.bg_stroke = Self::default_stroke();
|
|
|
|
|
ui.visuals_mut().widgets.hovered.bg_stroke = Self::hover_stroke();
|
|
|
|
|
ui.visuals_mut().widgets.active.bg_stroke = Self::item_stroke();
|
2023-07-30 10:52:24 +03:00
|
|
|
|
|
2023-08-09 02:22:16 +03:00
|
|
|
|
// Setup button text color.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
let text_color = if let Some(c) = color { c } else { Colors::item_button() };
|
2023-08-09 02:22:16 +03:00
|
|
|
|
|
2023-07-30 10:52:24 +03:00
|
|
|
|
// Show button.
|
2023-08-09 02:22:16 +03:00
|
|
|
|
let br = Button::new(RichText::new(text).size(20.0).color(text_color))
|
|
|
|
|
.rounding(rounding)
|
2023-07-30 10:52:24 +03:00
|
|
|
|
.min_size(button_size)
|
2023-08-13 19:44:41 +03:00
|
|
|
|
.ui(ui)
|
|
|
|
|
.on_hover_cursor(CursorIcon::PointingHand);
|
2023-08-05 22:39:42 +03:00
|
|
|
|
br.surrender_focus();
|
2023-07-30 10:52:24 +03:00
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(action)();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-08 01:00:56 +03:00
|
|
|
|
/// Default height of [`egui::TextEdit`] view.
|
|
|
|
|
const TEXT_EDIT_HEIGHT: f32 = 37.0;
|
|
|
|
|
|
|
|
|
|
/// Draw [`egui::TextEdit`] widget.
|
|
|
|
|
pub fn text_edit(ui: &mut egui::Ui,
|
|
|
|
|
cb: &dyn PlatformCallbacks,
|
|
|
|
|
value: &mut String,
|
2024-05-21 13:31:46 +03:00
|
|
|
|
options: &mut TextEditOptions) {
|
2023-11-08 01:00:56 +03:00
|
|
|
|
let mut layout_rect = ui.available_rect_before_wrap();
|
|
|
|
|
layout_rect.set_height(Self::TEXT_EDIT_HEIGHT);
|
|
|
|
|
ui.allocate_ui_with_layout(layout_rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
|
|
|
|
// Setup password button.
|
|
|
|
|
let mut show_pass = false;
|
|
|
|
|
if options.password {
|
|
|
|
|
// Set password button state value.
|
|
|
|
|
let show_pass_id = egui::Id::new(options.id).with("_show_pass");
|
|
|
|
|
show_pass = ui.data(|data| {
|
|
|
|
|
data.get_temp(show_pass_id)
|
|
|
|
|
}).unwrap_or(true);
|
|
|
|
|
// Draw button to show/hide current password.
|
|
|
|
|
let eye_icon = if show_pass { EYE } else { EYE_SLASH };
|
|
|
|
|
let mut changed = false;
|
2024-05-29 22:47:17 +03:00
|
|
|
|
View::button(ui, eye_icon.to_string(), Colors::white_or_black(false), || {
|
2023-11-08 01:00:56 +03:00
|
|
|
|
show_pass = !show_pass;
|
|
|
|
|
changed = true;
|
|
|
|
|
});
|
|
|
|
|
// Save state if changed.
|
|
|
|
|
if changed {
|
|
|
|
|
ui.data_mut(|data| {
|
|
|
|
|
data.insert_temp(show_pass_id, show_pass);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ui.add_space(8.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup copy button.
|
|
|
|
|
if options.copy {
|
|
|
|
|
let copy_icon = COPY.to_string();
|
2024-05-29 22:47:17 +03:00
|
|
|
|
View::button(ui, copy_icon, Colors::white_or_black(false), || {
|
2023-11-08 01:00:56 +03:00
|
|
|
|
cb.copy_string_to_buffer(value.clone());
|
|
|
|
|
});
|
|
|
|
|
ui.add_space(8.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup paste button.
|
|
|
|
|
if options.paste {
|
|
|
|
|
let paste_icon = CLIPBOARD_TEXT.to_string();
|
2024-05-29 22:47:17 +03:00
|
|
|
|
View::button(ui, paste_icon, Colors::white_or_black(false), || {
|
2023-11-08 01:00:56 +03:00
|
|
|
|
*value = cb.get_string_from_buffer();
|
|
|
|
|
});
|
|
|
|
|
ui.add_space(8.0);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 18:15:03 +03:00
|
|
|
|
// Setup scan QR code button.
|
2024-05-05 15:24:07 +03:00
|
|
|
|
if options.scan_qr {
|
2024-04-30 18:15:03 +03:00
|
|
|
|
let scan_icon = SCAN.to_string();
|
2024-05-29 22:47:17 +03:00
|
|
|
|
View::button(ui, scan_icon, Colors::white_or_black(false), || {
|
2024-05-21 13:31:46 +03:00
|
|
|
|
cb.start_camera();
|
|
|
|
|
options.scan_pressed = true;
|
2024-04-30 18:15:03 +03:00
|
|
|
|
});
|
|
|
|
|
ui.add_space(8.0);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-08 01:00:56 +03:00
|
|
|
|
let layout_size = ui.available_size();
|
|
|
|
|
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
|
|
|
|
// Setup text edit size.
|
|
|
|
|
let mut edit_rect = ui.available_rect_before_wrap();
|
|
|
|
|
edit_rect.set_height(Self::TEXT_EDIT_HEIGHT);
|
2024-04-16 15:24:22 +03:00
|
|
|
|
|
2023-11-08 01:00:56 +03:00
|
|
|
|
// Show text edit.
|
|
|
|
|
let text_edit_resp = egui::TextEdit::singleline(value)
|
|
|
|
|
.id(options.id)
|
2024-04-16 15:24:22 +03:00
|
|
|
|
.margin(egui::Vec2::new(2.0, 0.0))
|
2023-11-08 01:00:56 +03:00
|
|
|
|
.font(TextStyle::Heading)
|
|
|
|
|
.min_size(edit_rect.size())
|
|
|
|
|
.horizontal_align(if options.h_center { Align::Center } else { Align::Min })
|
|
|
|
|
.vertical_align(Align::Center)
|
|
|
|
|
.password(show_pass)
|
2024-04-16 15:24:22 +03:00
|
|
|
|
.cursor_at_end(true)
|
2023-11-08 01:00:56 +03:00
|
|
|
|
.ui(ui);
|
|
|
|
|
// Show keyboard on click.
|
|
|
|
|
if text_edit_resp.clicked() {
|
|
|
|
|
cb.show_keyboard();
|
|
|
|
|
}
|
|
|
|
|
// Setup focus on input field.
|
|
|
|
|
if options.focus {
|
|
|
|
|
text_edit_resp.request_focus();
|
|
|
|
|
cb.show_keyboard();
|
|
|
|
|
}
|
2024-04-16 15:24:22 +03:00
|
|
|
|
|
2024-05-18 22:50:08 +03:00
|
|
|
|
// Apply text from input on Android as temporary fix for egui.
|
|
|
|
|
if text_edit_resp.has_focus() {
|
|
|
|
|
Self::on_soft_input(ui, options.id, value);
|
2024-04-16 15:24:22 +03:00
|
|
|
|
}
|
2023-11-08 01:00:56 +03:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 22:50:08 +03:00
|
|
|
|
/// Apply soft keyboard input data to provided String.
|
2024-05-21 13:31:46 +03:00
|
|
|
|
pub fn on_soft_input(ui: &mut egui::Ui, id: egui::Id, value: &mut String) {
|
2024-05-18 22:50:08 +03:00
|
|
|
|
let os = OperatingSystem::from_target_os();
|
|
|
|
|
if os == OperatingSystem::Android {
|
|
|
|
|
let mut w_input = LAST_SOFT_KEYBOARD_INPUT.write();
|
|
|
|
|
|
|
|
|
|
if !w_input.is_empty() {
|
|
|
|
|
let mut state = TextEditState::load(ui.ctx(), id).unwrap();
|
|
|
|
|
match state.cursor.char_range() {
|
|
|
|
|
None => {}
|
|
|
|
|
Some(range) => {
|
|
|
|
|
let mut r = range.clone();
|
|
|
|
|
|
|
|
|
|
let mut index = r.primary.index;
|
|
|
|
|
|
|
|
|
|
value.insert_text(w_input.as_str(), index);
|
|
|
|
|
index = index + 1;
|
|
|
|
|
|
|
|
|
|
if index == 0 {
|
|
|
|
|
r.primary.index = value.len();
|
|
|
|
|
r.secondary.index = r.primary.index;
|
|
|
|
|
} else {
|
|
|
|
|
r.primary.index = index;
|
|
|
|
|
r.secondary.index = r.primary.index;
|
|
|
|
|
}
|
|
|
|
|
state.cursor.set_char_range(Some(r));
|
|
|
|
|
TextEditState::store(state, ui.ctx(), id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*w_input = "".to_string();
|
|
|
|
|
ui.ctx().request_repaint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-05 22:39:42 +03:00
|
|
|
|
/// Calculate item background/button rounding based on item index.
|
|
|
|
|
pub fn item_rounding(index: usize, len: usize, is_button: bool) -> Rounding {
|
|
|
|
|
let corners = if is_button {
|
|
|
|
|
if len == 1 {
|
|
|
|
|
[false, true, true, false]
|
|
|
|
|
} else if index == 0 {
|
|
|
|
|
[false, true, false, false]
|
|
|
|
|
} else if index == len - 1 {
|
|
|
|
|
[false, false, true, false]
|
|
|
|
|
} else {
|
|
|
|
|
[false, false, false, false]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if len == 1 {
|
|
|
|
|
[true, true, true, true]
|
|
|
|
|
} else if index == 0 {
|
|
|
|
|
[true, true, false, false]
|
|
|
|
|
} else if index == len - 1 {
|
|
|
|
|
[false, false, true, true]
|
|
|
|
|
} else {
|
|
|
|
|
[false, false, false, false]
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-07-30 10:52:24 +03:00
|
|
|
|
Rounding {
|
|
|
|
|
nw: if corners[0] { 8.0 } else { 0.0 },
|
|
|
|
|
ne: if corners[1] { 8.0 } else { 0.0 },
|
|
|
|
|
sw: if corners[3] { 8.0 } else { 0.0 },
|
|
|
|
|
se: if corners[2] { 8.0 } else { 0.0 },
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-02 02:05:34 +03:00
|
|
|
|
/// Draw rounded box with some value and label in the middle,
|
|
|
|
|
/// where is r = (top_left, top_right, bottom_left, bottom_right).
|
2023-05-18 03:53:38 +03:00
|
|
|
|
/// | VALUE |
|
|
|
|
|
/// | label |
|
|
|
|
|
pub fn rounded_box(ui: &mut egui::Ui, value: String, label: String, r: [bool; 4]) {
|
2023-06-15 23:54:41 +03:00
|
|
|
|
let rect = ui.available_rect_before_wrap();
|
2023-05-18 03:53:38 +03:00
|
|
|
|
|
2023-06-03 21:35:38 +03:00
|
|
|
|
// Create background shape.
|
|
|
|
|
let mut bg_shape = RectShape {
|
2023-05-18 03:53:38 +03:00
|
|
|
|
rect,
|
2023-06-03 21:35:38 +03:00
|
|
|
|
rounding: Rounding {
|
2023-05-18 03:53:38 +03:00
|
|
|
|
nw: if r[0] { 8.0 } else { 0.0 },
|
|
|
|
|
ne: if r[1] { 8.0 } else { 0.0 },
|
|
|
|
|
sw: if r[2] { 8.0 } else { 0.0 },
|
|
|
|
|
se: if r[3] { 8.0 } else { 0.0 },
|
|
|
|
|
},
|
2023-08-16 04:40:19 +03:00
|
|
|
|
fill: Colors::TRANSPARENT,
|
2024-05-29 22:47:17 +03:00
|
|
|
|
stroke: Self::item_stroke(),
|
2023-10-04 16:16:30 +03:00
|
|
|
|
fill_texture_id: Default::default(),
|
|
|
|
|
uv: Rect::ZERO
|
2023-06-03 21:35:38 +03:00
|
|
|
|
};
|
|
|
|
|
let bg_idx = ui.painter().add(bg_shape);
|
|
|
|
|
|
|
|
|
|
// Draw box content.
|
|
|
|
|
let content_resp = ui.allocate_ui_at_rect(rect, |ui| {
|
|
|
|
|
ui.vertical_centered_justified(|ui| {
|
2023-06-20 00:38:25 +03:00
|
|
|
|
ui.add_space(2.0);
|
|
|
|
|
|
|
|
|
|
ui.scope(|ui| {
|
|
|
|
|
// Correct vertical spacing between items.
|
|
|
|
|
ui.style_mut().spacing.item_spacing.y = -3.0;
|
|
|
|
|
|
|
|
|
|
// Draw box value.
|
|
|
|
|
let mut job = LayoutJob::single_section(value, TextFormat {
|
2023-07-14 03:51:06 +03:00
|
|
|
|
font_id: FontId::proportional(17.0),
|
2024-05-29 22:47:17 +03:00
|
|
|
|
color: Colors::white_or_black(true),
|
2023-06-20 00:38:25 +03:00
|
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
job.wrap = TextWrapping {
|
|
|
|
|
max_rows: 1,
|
2023-08-09 02:51:50 +03:00
|
|
|
|
break_anywhere: true,
|
2023-06-20 00:38:25 +03:00
|
|
|
|
overflow_character: Option::from('﹍'),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
ui.label(job);
|
|
|
|
|
|
|
|
|
|
// Draw box label.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.label(RichText::new(label).color(Colors::gray()).size(15.0));
|
2023-06-03 21:35:38 +03:00
|
|
|
|
});
|
2023-06-20 00:38:25 +03:00
|
|
|
|
|
|
|
|
|
ui.add_space(2.0);
|
2023-05-18 03:53:38 +03:00
|
|
|
|
});
|
2023-06-03 21:35:38 +03:00
|
|
|
|
}).response;
|
|
|
|
|
|
|
|
|
|
// Setup background shape to be painted behind box content.
|
|
|
|
|
bg_shape.rect = content_resp.rect;
|
|
|
|
|
ui.painter().set(bg_idx, bg_shape);
|
2023-05-18 03:53:38 +03:00
|
|
|
|
}
|
2023-06-02 21:19:34 +03:00
|
|
|
|
|
|
|
|
|
/// Draw content in the center of current layout with specified width and height.
|
2023-06-03 21:35:38 +03:00
|
|
|
|
pub fn center_content(ui: &mut egui::Ui, height: f32, content: impl FnOnce(&mut egui::Ui)) {
|
2023-06-02 21:19:34 +03:00
|
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
|
let mut rect = ui.available_rect_before_wrap();
|
2024-04-17 01:30:28 +03:00
|
|
|
|
let side_margin = 28.0;
|
2023-06-03 21:35:38 +03:00
|
|
|
|
rect.min += egui::emath::vec2(side_margin, ui.available_height() / 2.0 - height / 2.0);
|
2023-06-02 21:19:34 +03:00
|
|
|
|
rect.max -= egui::emath::vec2(side_margin, 0.0);
|
|
|
|
|
ui.allocate_ui_at_rect(rect, |ui| {
|
|
|
|
|
(content)(ui);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-06-17 13:23:31 +03:00
|
|
|
|
|
2024-05-03 19:51:57 +03:00
|
|
|
|
/// Size of big loading spinner.
|
|
|
|
|
pub const BIG_SPINNER_SIZE: f32 = 104.0;
|
|
|
|
|
|
2023-06-17 13:23:31 +03:00
|
|
|
|
/// Draw big gold loading spinner.
|
|
|
|
|
pub fn big_loading_spinner(ui: &mut egui::Ui) {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
Spinner::new().size(Self::BIG_SPINNER_SIZE).color(Colors::gold()).ui(ui);
|
2023-06-17 13:23:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draw small gold loading spinner.
|
|
|
|
|
pub fn small_loading_spinner(ui: &mut egui::Ui) {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
Spinner::new().size(38.0).color(Colors::gold()).ui(ui);
|
2023-06-17 13:23:31 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 00:38:25 +03:00
|
|
|
|
/// Draw the button that looks like checkbox with callback on check.
|
2023-06-19 01:29:15 +03:00
|
|
|
|
pub fn checkbox(ui: &mut egui::Ui, checked: bool, text: String, callback: impl FnOnce()) {
|
2023-06-20 00:38:25 +03:00
|
|
|
|
let (text_value, color) = match checked {
|
2024-05-29 22:47:17 +03:00
|
|
|
|
true => (format!("{} {}", CHECK_SQUARE, text), Colors::text_button()),
|
|
|
|
|
false => (format!("{} {}", SQUARE, text), Colors::checkbox())
|
2023-06-17 13:23:31 +03:00
|
|
|
|
};
|
2023-06-27 02:11:07 +03:00
|
|
|
|
|
2023-07-14 03:51:06 +03:00
|
|
|
|
let br = Button::new(RichText::new(text_value).size(17.0).color(color))
|
2023-06-20 00:38:25 +03:00
|
|
|
|
.frame(false)
|
|
|
|
|
.stroke(Stroke::NONE)
|
|
|
|
|
.fill(Colors::TRANSPARENT)
|
2023-08-13 19:44:41 +03:00
|
|
|
|
.ui(ui)
|
|
|
|
|
.on_hover_cursor(CursorIcon::PointingHand);
|
2023-06-21 02:13:47 +03:00
|
|
|
|
if Self::touched(ui, br) {
|
|
|
|
|
(callback)();
|
|
|
|
|
}
|
2023-06-20 00:38:25 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-21 02:13:47 +03:00
|
|
|
|
/// Show a [`RadioButton`]. It is selected if `*current_value == selected_value`.
|
|
|
|
|
/// If clicked, `selected_value` is assigned to `*current_value`.
|
|
|
|
|
pub fn radio_value<T: PartialEq>(ui: &mut egui::Ui, current: &mut T, value: T, text: String) {
|
2024-05-17 21:37:29 +03:00
|
|
|
|
ui.scope(|ui| {
|
|
|
|
|
// Setup background color.
|
2024-05-29 22:47:17 +03:00
|
|
|
|
ui.visuals_mut().widgets.inactive.bg_fill = Colors::fill_deep();
|
2024-05-17 21:37:29 +03:00
|
|
|
|
// Draw radio button.
|
|
|
|
|
let mut response = ui.radio(*current == value, text)
|
|
|
|
|
.on_hover_cursor(CursorIcon::PointingHand);
|
|
|
|
|
if Self::touched(ui, response.clone()) && *current != value {
|
|
|
|
|
*current = value;
|
|
|
|
|
response.mark_changed();
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-06-21 02:13:47 +03:00
|
|
|
|
}
|
2023-06-17 13:23:31 +03:00
|
|
|
|
|
2023-08-18 15:43:06 +03:00
|
|
|
|
/// Draw horizontal line.
|
2023-06-21 02:13:47 +03:00
|
|
|
|
pub fn horizontal_line(ui: &mut egui::Ui, color: Color32) {
|
|
|
|
|
let line_size = egui::Vec2::new(ui.available_width(), 1.0);
|
|
|
|
|
let (line_rect, _) = ui.allocate_exact_size(line_size, Sense::hover());
|
|
|
|
|
let painter = ui.painter();
|
|
|
|
|
painter.hline(line_rect.x_range(),
|
|
|
|
|
painter.round_to_pixel(line_rect.center().y),
|
|
|
|
|
Stroke { width: 1.0, color });
|
2023-06-17 13:23:31 +03:00
|
|
|
|
}
|
2023-07-16 11:23:56 +03:00
|
|
|
|
|
2023-08-18 15:43:06 +03:00
|
|
|
|
/// Format timestamp in seconds with local UTC offset.
|
|
|
|
|
pub fn format_time(ts: i64) -> String {
|
|
|
|
|
let utc_offset = chrono::Local::now().offset().local_minus_utc();
|
|
|
|
|
let utc_time = ts + utc_offset as i64;
|
2024-04-20 16:59:54 +03:00
|
|
|
|
let tx_time = chrono::DateTime::from_timestamp(utc_time, 0).unwrap();
|
2023-08-18 15:43:06 +03:00
|
|
|
|
tx_time.format("%d/%m/%Y %H:%M:%S").to_string()
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-16 11:23:56 +03:00
|
|
|
|
/// Get top display inset (cutout) size.
|
|
|
|
|
pub fn get_top_inset() -> f32 {
|
|
|
|
|
TOP_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get right display inset (cutout) size.
|
|
|
|
|
pub fn get_right_inset() -> f32 {
|
|
|
|
|
RIGHT_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get bottom display inset (cutout) size.
|
|
|
|
|
pub fn get_bottom_inset() -> f32 {
|
|
|
|
|
BOTTOM_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get left display inset (cutout) size.
|
|
|
|
|
pub fn get_left_inset() -> f32 {
|
|
|
|
|
LEFT_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
|
static ref TOP_DISPLAY_INSET: AtomicI32 = AtomicI32::new(0);
|
|
|
|
|
static ref RIGHT_DISPLAY_INSET: AtomicI32 = AtomicI32::new(0);
|
|
|
|
|
static ref BOTTOM_DISPLAY_INSET: AtomicI32 = AtomicI32::new(0);
|
|
|
|
|
static ref LEFT_DISPLAY_INSET: AtomicI32 = AtomicI32::new(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
/// Callback from Java code to update display insets (cutouts).
|
|
|
|
|
pub extern "C" fn Java_mw_gri_android_MainActivity_onDisplayInsets(
|
|
|
|
|
_env: jni::JNIEnv,
|
|
|
|
|
_class: jni::objects::JObject,
|
|
|
|
|
cutouts: jni::sys::jarray
|
|
|
|
|
) {
|
|
|
|
|
use jni::objects::{JObject, JPrimitiveArray};
|
|
|
|
|
|
|
|
|
|
let mut array: [i32; 4] = [0; 4];
|
|
|
|
|
unsafe {
|
|
|
|
|
let j_obj = JObject::from_raw(cutouts);
|
|
|
|
|
let j_arr = JPrimitiveArray::from(j_obj);
|
|
|
|
|
_env.get_int_array_region(j_arr, 0, array.as_mut()).unwrap();
|
|
|
|
|
}
|
|
|
|
|
TOP_DISPLAY_INSET.store(array[0], Ordering::Relaxed);
|
|
|
|
|
RIGHT_DISPLAY_INSET.store(array[1], Ordering::Relaxed);
|
|
|
|
|
BOTTOM_DISPLAY_INSET.store(array[2], Ordering::Relaxed);
|
|
|
|
|
LEFT_DISPLAY_INSET.store(array[3], Ordering::Relaxed);
|
2024-04-16 15:24:22 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lazy_static! {
|
2024-05-18 22:52:20 +03:00
|
|
|
|
static ref LAST_SOFT_KEYBOARD_INPUT: Arc<RwLock<String>> = Arc::new(RwLock::new("".into()));
|
2024-04-16 15:24:22 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
|
#[allow(non_snake_case)]
|
|
|
|
|
#[no_mangle]
|
|
|
|
|
/// Callback from Java code with last entered character from soft keyboard.
|
|
|
|
|
pub extern "C" fn Java_mw_gri_android_MainActivity_onInput(
|
|
|
|
|
_env: jni::JNIEnv,
|
|
|
|
|
_class: jni::objects::JObject,
|
|
|
|
|
char: jni::sys::jstring
|
|
|
|
|
) {
|
2024-05-15 20:51:14 +03:00
|
|
|
|
use jni::objects::JString;
|
2024-05-15 21:17:18 +03:00
|
|
|
|
use std::ops::Add;
|
2024-04-16 15:24:22 +03:00
|
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
let j_obj = JString::from_raw(char);
|
|
|
|
|
let j_str = _env.get_string_unchecked(j_obj.as_ref()).unwrap();
|
|
|
|
|
match j_str.to_str() {
|
|
|
|
|
Ok(str) => {
|
2024-05-15 14:47:19 +03:00
|
|
|
|
let mut w_input = LAST_SOFT_KEYBOARD_INPUT.write();
|
2024-04-16 15:24:22 +03:00
|
|
|
|
*w_input = w_input.clone().add(str);
|
|
|
|
|
}
|
|
|
|
|
Err(_) => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-18 03:53:38 +03:00
|
|
|
|
}
|