New Plugin trait (#7385)
This adds a new `Plugin` trait and new `input_hook` and `output_hook` plugin fns. Having a `Plugin` trait should make it easier to store state in the plugin and improve discoverability of possible plugin hooks. The old `on_begin_pass` and `on_end_pass` have been ported to use the new plugin trait, should we deprecate them?
This commit is contained in:
parent
226bdc4c5b
commit
f2f00ef62a
|
|
@ -17,31 +17,32 @@ use epaint::{
|
||||||
use crate::{
|
use crate::{
|
||||||
Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport,
|
Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport,
|
||||||
ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory,
|
ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory,
|
||||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
ModifierNames, Modifiers, NumExt as _, Order, Painter, Plugin, RawInput, Response, RichText,
|
||||||
ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder,
|
ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder,
|
||||||
ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput,
|
ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput,
|
||||||
Widget as _, WidgetRect, WidgetText,
|
Widget as _, WidgetRect, WidgetText,
|
||||||
animation_manager::AnimationManager,
|
animation_manager::AnimationManager,
|
||||||
containers::{self, area::AreaState},
|
containers::{self, area::AreaState},
|
||||||
data::output::PlatformOutput,
|
data::output::PlatformOutput,
|
||||||
epaint, hit_test,
|
epaint,
|
||||||
input_state::{InputState, MultiTouchInfo, PointerEvent},
|
hit_test::WidgetHits,
|
||||||
interaction,
|
input_state::{InputState, MultiTouchInfo, PointerEvent, SurrenderFocusOn},
|
||||||
|
interaction::InteractionSnapshot,
|
||||||
layers::GraphicLayers,
|
layers::GraphicLayers,
|
||||||
load::{self, Bytes, Loaders, SizedTexture},
|
load::{self, Bytes, Loaders, SizedTexture},
|
||||||
memory::{Options, Theme},
|
memory::{Options, Theme},
|
||||||
os::OperatingSystem,
|
os::OperatingSystem,
|
||||||
output::FullOutput,
|
output::FullOutput,
|
||||||
pass_state::PassState,
|
pass_state::PassState,
|
||||||
|
plugin,
|
||||||
|
plugin::TypedPluginHandle,
|
||||||
resize, response, scroll_area,
|
resize, response, scroll_area,
|
||||||
util::IdTypeMap,
|
util::IdTypeMap,
|
||||||
viewport::ViewportClass,
|
viewport::ViewportClass,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{hit_test::WidgetHits, interaction::InteractionSnapshot};
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
use crate::IdMap;
|
use crate::IdMap;
|
||||||
use crate::input_state::SurrenderFocusOn;
|
|
||||||
|
|
||||||
/// Information given to the backend about when it is time to repaint the ui.
|
/// Information given to the backend about when it is time to repaint the ui.
|
||||||
///
|
///
|
||||||
|
|
@ -93,46 +94,6 @@ impl Default for WrappedTextureManager {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Generic event callback.
|
|
||||||
pub type ContextCallback = Arc<dyn Fn(&Context) + Send + Sync>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct NamedContextCallback {
|
|
||||||
debug_name: &'static str,
|
|
||||||
callback: ContextCallback,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Callbacks that users can register
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
struct Plugins {
|
|
||||||
pub on_begin_pass: Vec<NamedContextCallback>,
|
|
||||||
pub on_end_pass: Vec<NamedContextCallback>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugins {
|
|
||||||
fn call(ctx: &Context, _cb_name: &str, callbacks: &[NamedContextCallback]) {
|
|
||||||
profiling::scope!("plugins", _cb_name);
|
|
||||||
for NamedContextCallback {
|
|
||||||
debug_name: _name,
|
|
||||||
callback,
|
|
||||||
} in callbacks
|
|
||||||
{
|
|
||||||
profiling::scope!("plugin", _name);
|
|
||||||
(callback)(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_begin_pass(&self, ctx: &Context) {
|
|
||||||
Self::call(ctx, "on_begin_pass", &self.on_begin_pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_end_pass(&self, ctx: &Context) {
|
|
||||||
Self::call(ctx, "on_end_pass", &self.on_end_pass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Repaint-logic
|
/// Repaint-logic
|
||||||
impl ContextImpl {
|
impl ContextImpl {
|
||||||
/// This is where we update the repaint logic.
|
/// This is where we update the repaint logic.
|
||||||
|
|
@ -412,7 +373,7 @@ struct ContextImpl {
|
||||||
memory: Memory,
|
memory: Memory,
|
||||||
animation_manager: AnimationManager,
|
animation_manager: AnimationManager,
|
||||||
|
|
||||||
plugins: Plugins,
|
plugins: plugin::Plugins,
|
||||||
|
|
||||||
/// All viewports share the same texture manager and texture namespace.
|
/// All viewports share the same texture manager and texture namespace.
|
||||||
///
|
///
|
||||||
|
|
@ -756,10 +717,12 @@ impl Default for Context {
|
||||||
};
|
};
|
||||||
let ctx = Self(Arc::new(RwLock::new(ctx_impl)));
|
let ctx = Self(Arc::new(RwLock::new(ctx_impl)));
|
||||||
|
|
||||||
|
ctx.add_plugin(plugin::CallbackPlugin::default());
|
||||||
|
|
||||||
// Register built-in plugins:
|
// Register built-in plugins:
|
||||||
crate::debug_text::register(&ctx);
|
ctx.add_plugin(crate::debug_text::DebugTextPlugin::default());
|
||||||
crate::text_selection::LabelSelectionState::register(&ctx);
|
ctx.add_plugin(crate::text_selection::LabelSelectionState::default());
|
||||||
crate::DragAndDrop::register(&ctx);
|
ctx.add_plugin(crate::DragAndDrop::default());
|
||||||
|
|
||||||
ctx
|
ctx
|
||||||
}
|
}
|
||||||
|
|
@ -889,13 +852,16 @@ impl Context {
|
||||||
/// let full_output = ctx.end_pass();
|
/// let full_output = ctx.end_pass();
|
||||||
/// // handle full_output
|
/// // handle full_output
|
||||||
/// ```
|
/// ```
|
||||||
pub fn begin_pass(&self, new_input: RawInput) {
|
pub fn begin_pass(&self, mut new_input: RawInput) {
|
||||||
profiling::function_scope!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
|
||||||
|
plugins.on_input(&mut new_input);
|
||||||
|
|
||||||
self.write(|ctx| ctx.begin_pass(new_input));
|
self.write(|ctx| ctx.begin_pass(new_input));
|
||||||
|
|
||||||
// Plugins run just after the pass starts:
|
// Plugins run just after the pass starts:
|
||||||
self.read(|ctx| ctx.plugins.clone()).on_begin_pass(self);
|
plugins.on_begin_pass(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See [`Self::begin_pass`].
|
/// See [`Self::begin_pass`].
|
||||||
|
|
@ -1885,26 +1851,73 @@ impl Context {
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Call the given callback at the start of each pass of each viewport.
|
/// Call the given callback at the start of each pass of each viewport.
|
||||||
///
|
///
|
||||||
/// This can be used for egui _plugins_.
|
/// This is a convenience wrapper around [`Self::add_plugin`].
|
||||||
/// See [`crate::debug_text`] for an example.
|
pub fn on_begin_pass(&self, debug_name: &'static str, cb: plugin::ContextCallback) {
|
||||||
pub fn on_begin_pass(&self, debug_name: &'static str, cb: ContextCallback) {
|
self.with_plugin(|p: &mut crate::plugin::CallbackPlugin| {
|
||||||
let named_cb = NamedContextCallback {
|
p.on_begin_plugins.push((debug_name, cb));
|
||||||
debug_name,
|
});
|
||||||
callback: cb,
|
|
||||||
};
|
|
||||||
self.write(|ctx| ctx.plugins.on_begin_pass.push(named_cb));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the given callback at the end of each pass of each viewport.
|
/// Call the given callback at the end of each pass of each viewport.
|
||||||
///
|
///
|
||||||
/// This can be used for egui _plugins_.
|
/// This is a convenience wrapper around [`Self::add_plugin`].
|
||||||
/// See [`crate::debug_text`] for an example.
|
pub fn on_end_pass(&self, debug_name: &'static str, cb: plugin::ContextCallback) {
|
||||||
pub fn on_end_pass(&self, debug_name: &'static str, cb: ContextCallback) {
|
self.with_plugin(|p: &mut crate::plugin::CallbackPlugin| {
|
||||||
let named_cb = NamedContextCallback {
|
p.on_end_plugins.push((debug_name, cb));
|
||||||
debug_name,
|
});
|
||||||
callback: cb,
|
}
|
||||||
};
|
|
||||||
self.write(|ctx| ctx.plugins.on_end_pass.push(named_cb));
|
/// Register a [`Plugin`]
|
||||||
|
///
|
||||||
|
/// Plugins are called in the order they are added.
|
||||||
|
///
|
||||||
|
/// A plugin of the same type can only be added once (further calls with the same type will be ignored).
|
||||||
|
/// This way it's convenient to add plugins in `eframe::run_simple_native`.
|
||||||
|
pub fn add_plugin(&self, plugin: impl Plugin + 'static) {
|
||||||
|
let handle = plugin::PluginHandle::new(plugin);
|
||||||
|
|
||||||
|
let added = self.write(|ctx| ctx.plugins.add(handle.clone()));
|
||||||
|
|
||||||
|
if added {
|
||||||
|
handle.lock().dyn_plugin_mut().setup(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the provided closure with the plugin of type `T`, if it was registered.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the plugin was not registered.
|
||||||
|
pub fn with_plugin<T: Plugin + 'static, R>(&self, f: impl FnOnce(&mut T) -> R) -> Option<R> {
|
||||||
|
let plugin = self.read(|ctx| ctx.plugins.get(std::any::TypeId::of::<T>()));
|
||||||
|
plugin.map(|plugin| f(plugin.lock().typed_plugin_mut()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the plugin of type `T`.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
/// If the plugin of type `T` was not registered, this will panic.
|
||||||
|
pub fn plugin<T: Plugin>(&self) -> TypedPluginHandle<T> {
|
||||||
|
if let Some(plugin) = self.plugin_opt() {
|
||||||
|
plugin
|
||||||
|
} else {
|
||||||
|
panic!("Plugin of type {:?} not found", std::any::type_name::<T>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the plugin of type `T`, if it was registered.
|
||||||
|
pub fn plugin_opt<T: Plugin>(&self) -> Option<TypedPluginHandle<T>> {
|
||||||
|
let plugin = self.read(|ctx| ctx.plugins.get(std::any::TypeId::of::<T>()));
|
||||||
|
plugin.map(TypedPluginHandle::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the plugin of type `T`, or insert its default.
|
||||||
|
pub fn plugin_or_default<T: Plugin + Default>(&self) -> TypedPluginHandle<T> {
|
||||||
|
if let Some(plugin) = self.plugin_opt() {
|
||||||
|
plugin
|
||||||
|
} else {
|
||||||
|
let default_plugin = T::default();
|
||||||
|
self.add_plugin(default_plugin);
|
||||||
|
self.plugin()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2265,12 +2278,15 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plugins run just before the pass ends.
|
// Plugins run just before the pass ends.
|
||||||
self.read(|ctx| ctx.plugins.clone()).on_end_pass(self);
|
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
|
||||||
|
plugins.on_end_pass(self);
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
self.debug_painting();
|
self.debug_painting();
|
||||||
|
|
||||||
self.write(|ctx| ctx.end_pass())
|
let mut output = self.write(|ctx| ctx.end_pass());
|
||||||
|
plugins.on_output(&mut output);
|
||||||
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call at the end of each frame if you called [`Context::begin_pass`].
|
/// Call at the end of each frame if you called [`Context::begin_pass`].
|
||||||
|
|
@ -3170,7 +3186,9 @@ impl Context {
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"{:#?}",
|
"{:#?}",
|
||||||
crate::text_selection::LabelSelectionState::load(ui.ctx())
|
*ui.ctx()
|
||||||
|
.plugin::<crate::text_selection::LabelSelectionState>()
|
||||||
|
.lock()
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,14 @@
|
||||||
//! This is an example of how to create a plugin for egui.
|
//! This is an example of how to create a plugin for egui.
|
||||||
//!
|
//!
|
||||||
//! A plugin usually consist of a struct that holds some state,
|
//! A plugin is a struct that implements the [`Plugin`] trait and holds some state.
|
||||||
//! which is stored using [`Context::data_mut`].
|
//! The plugin is registered with the [`Context`] using [`Context::add_plugin`]
|
||||||
//! The plugin registers itself onto a specific [`Context`]
|
//! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]).
|
||||||
//! to get callbacks on certain events ([`Context::on_begin_pass`], [`Context::on_end_pass`]).
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, text,
|
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Vec2, WidgetText,
|
||||||
|
text,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Register this plugin on the given egui context,
|
|
||||||
/// so that it will be called every pass.
|
|
||||||
///
|
|
||||||
/// This is a built-in plugin in egui,
|
|
||||||
/// meaning [`Context`] calls this from its `Default` implementation,
|
|
||||||
/// so this is marked as `pub(crate)`.
|
|
||||||
pub(crate) fn register(ctx: &Context) {
|
|
||||||
ctx.on_end_pass("debug_text", std::sync::Arc::new(State::end_pass));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print this text next to the cursor at the end of the pass.
|
/// Print this text next to the cursor at the end of the pass.
|
||||||
///
|
///
|
||||||
/// If you call this multiple times, the text will be appended.
|
/// If you call this multiple times, the text will be appended.
|
||||||
|
|
@ -38,16 +28,13 @@ pub fn print(ctx: &Context, text: impl Into<WidgetText>) {
|
||||||
|
|
||||||
let location = std::panic::Location::caller();
|
let location = std::panic::Location::caller();
|
||||||
let location = format!("{}:{}", location.file(), location.line());
|
let location = format!("{}:{}", location.file(), location.line());
|
||||||
ctx.data_mut(|data| {
|
|
||||||
// We use `Id::NULL` as the id, since we only have one instance of this plugin.
|
let plugin = ctx.plugin::<DebugTextPlugin>();
|
||||||
// We use the `temp` version instead of `persisted` since we don't want to
|
let mut state = plugin.lock();
|
||||||
// persist state on disk when the egui app is closed.
|
|
||||||
let state = data.get_temp_mut_or_default::<State>(Id::NULL);
|
|
||||||
state.entries.push(Entry {
|
state.entries.push(Entry {
|
||||||
location,
|
location,
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -58,24 +45,26 @@ struct Entry {
|
||||||
|
|
||||||
/// A plugin for easily showing debug-text on-screen.
|
/// A plugin for easily showing debug-text on-screen.
|
||||||
///
|
///
|
||||||
/// This is a built-in plugin in egui.
|
/// This is a built-in plugin in egui, automatically registered during [`Context`] creation.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct State {
|
pub struct DebugTextPlugin {
|
||||||
// This gets re-filled every pass.
|
// This gets re-filled every pass.
|
||||||
entries: Vec<Entry>,
|
entries: Vec<Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl Plugin for DebugTextPlugin {
|
||||||
fn end_pass(ctx: &Context) {
|
fn debug_name(&self) -> &'static str {
|
||||||
let state = ctx.data_mut(|data| data.remove_temp::<Self>(Id::NULL));
|
"DebugTextPlugin"
|
||||||
if let Some(state) = state {
|
}
|
||||||
state.paint(ctx);
|
|
||||||
|
fn on_end_pass(&mut self, ctx: &Context) {
|
||||||
|
let entries = std::mem::take(&mut self.entries);
|
||||||
|
Self::paint_entries(ctx, entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(self, ctx: &Context) {
|
impl DebugTextPlugin {
|
||||||
let Self { entries } = self;
|
fn paint_entries(ctx: &Context, entries: Vec<Entry>) {
|
||||||
|
|
||||||
if entries.is_empty() {
|
if entries.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,46 @@
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{Context, CursorIcon, Id};
|
use crate::{Context, CursorIcon, Plugin};
|
||||||
|
|
||||||
/// Tracking of drag-and-drop payload.
|
/// Plugin for tracking drag-and-drop payload.
|
||||||
///
|
///
|
||||||
/// This is a low-level API.
|
/// This plugin stores the current drag-and-drop payload internally and handles
|
||||||
|
/// automatic cleanup when the drag operation ends (via Escape key or mouse release).
|
||||||
///
|
///
|
||||||
/// For a higher-level API, see:
|
/// This is a low-level API. For a higher-level API, see:
|
||||||
/// - [`crate::Ui::dnd_drag_source`]
|
/// - [`crate::Ui::dnd_drag_source`]
|
||||||
/// - [`crate::Ui::dnd_drop_zone`]
|
/// - [`crate::Ui::dnd_drop_zone`]
|
||||||
/// - [`crate::Response::dnd_set_drag_payload`]
|
/// - [`crate::Response::dnd_set_drag_payload`]
|
||||||
/// - [`crate::Response::dnd_hover_payload`]
|
/// - [`crate::Response::dnd_hover_payload`]
|
||||||
/// - [`crate::Response::dnd_release_payload`]
|
/// - [`crate::Response::dnd_release_payload`]
|
||||||
///
|
///
|
||||||
|
/// This is a built-in plugin in egui, automatically registered during [`Context`] creation.
|
||||||
|
///
|
||||||
/// See [this example](https://github.com/emilk/egui/blob/main/crates/egui_demo_lib/src/demo/drag_and_drop.rs).
|
/// See [this example](https://github.com/emilk/egui/blob/main/crates/egui_demo_lib/src/demo/drag_and_drop.rs).
|
||||||
#[doc(alias = "drag and drop")]
|
#[doc(alias = "drag and drop")]
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DragAndDrop {
|
pub struct DragAndDrop {
|
||||||
/// If set, something is currently being dragged
|
/// The current drag-and-drop payload, if any. Automatically cleared when drag ends.
|
||||||
payload: Option<Arc<dyn Any + Send + Sync>>,
|
payload: Option<Arc<dyn Any + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DragAndDrop {
|
impl Plugin for DragAndDrop {
|
||||||
pub(crate) fn register(ctx: &Context) {
|
fn debug_name(&self) -> &'static str {
|
||||||
ctx.on_begin_pass("drag_and_drop_begin_pass", Arc::new(Self::begin_pass));
|
"DragAndDrop"
|
||||||
ctx.on_end_pass("drag_and_drop_end_pass", Arc::new(Self::end_pass));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interrupt drag-and-drop if the user presses the escape key.
|
/// Interrupt drag-and-drop if the user presses the escape key.
|
||||||
///
|
///
|
||||||
/// This needs to happen at frame start so we can properly capture the escape key.
|
/// This needs to happen at frame start so we can properly capture the escape key.
|
||||||
fn begin_pass(ctx: &Context) {
|
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||||
let has_any_payload = Self::has_any_payload(ctx);
|
let has_any_payload = self.payload.is_some();
|
||||||
|
|
||||||
if has_any_payload {
|
if has_any_payload {
|
||||||
let abort_dnd_due_to_escape_key =
|
let abort_dnd_due_to_escape_key =
|
||||||
ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
||||||
|
|
||||||
if abort_dnd_due_to_escape_key {
|
if abort_dnd_due_to_escape_key {
|
||||||
Self::clear_payload(ctx);
|
self.payload = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,14 +50,14 @@ impl DragAndDrop {
|
||||||
/// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
|
/// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
|
||||||
/// This must happen at end-of-frame such that we don't shadow the mouse release event from user
|
/// This must happen at end-of-frame such that we don't shadow the mouse release event from user
|
||||||
/// code.
|
/// code.
|
||||||
fn end_pass(ctx: &Context) {
|
fn on_end_pass(&mut self, ctx: &Context) {
|
||||||
let has_any_payload = Self::has_any_payload(ctx);
|
let has_any_payload = self.payload.is_some();
|
||||||
|
|
||||||
if has_any_payload {
|
if has_any_payload {
|
||||||
let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released());
|
let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released());
|
||||||
|
|
||||||
if abort_dnd_due_to_mouse_release {
|
if abort_dnd_due_to_mouse_release {
|
||||||
Self::clear_payload(ctx);
|
self.payload = None;
|
||||||
} else {
|
} else {
|
||||||
// We set the cursor icon only if its default, as the user code might have
|
// We set the cursor icon only if its default, as the user code might have
|
||||||
// explicitly set it already.
|
// explicitly set it already.
|
||||||
|
|
@ -67,7 +69,9 @@ impl DragAndDrop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DragAndDrop {
|
||||||
/// Set a drag-and-drop payload.
|
/// Set a drag-and-drop payload.
|
||||||
///
|
///
|
||||||
/// This can be read by [`Self::payload`] until the pointer is released.
|
/// This can be read by [`Self::payload`] until the pointer is released.
|
||||||
|
|
@ -75,18 +79,12 @@ impl DragAndDrop {
|
||||||
where
|
where
|
||||||
Payload: Any + Send + Sync,
|
Payload: Any + Send + Sync,
|
||||||
{
|
{
|
||||||
ctx.data_mut(|data| {
|
ctx.plugin::<Self>().lock().payload = Some(Arc::new(payload));
|
||||||
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
|
|
||||||
state.payload = Some(Arc::new(payload));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the payload, setting it to `None`.
|
/// Clears the payload, setting it to `None`.
|
||||||
pub fn clear_payload(ctx: &Context) {
|
pub fn clear_payload(ctx: &Context) {
|
||||||
ctx.data_mut(|data| {
|
ctx.plugin::<Self>().lock().payload = None;
|
||||||
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
|
|
||||||
state.payload = None;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the payload, if any.
|
/// Retrieve the payload, if any.
|
||||||
|
|
@ -99,11 +97,13 @@ impl DragAndDrop {
|
||||||
where
|
where
|
||||||
Payload: Any + Send + Sync,
|
Payload: Any + Send + Sync,
|
||||||
{
|
{
|
||||||
ctx.data(|data| {
|
ctx.plugin::<Self>()
|
||||||
let state = data.get_temp::<Self>(Id::NULL)?;
|
.lock()
|
||||||
let payload = state.payload?;
|
.payload
|
||||||
payload.downcast().ok()
|
.as_ref()?
|
||||||
})
|
.clone()
|
||||||
|
.downcast()
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve and clear the payload, if any.
|
/// Retrieve and clear the payload, if any.
|
||||||
|
|
@ -116,11 +116,7 @@ impl DragAndDrop {
|
||||||
where
|
where
|
||||||
Payload: Any + Send + Sync,
|
Payload: Any + Send + Sync,
|
||||||
{
|
{
|
||||||
ctx.data_mut(|data| {
|
ctx.plugin::<Self>().lock().payload.take()?.downcast().ok()
|
||||||
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
|
|
||||||
let payload = state.payload.take()?;
|
|
||||||
payload.downcast().ok()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are we carrying a payload of the given type?
|
/// Are we carrying a payload of the given type?
|
||||||
|
|
@ -139,9 +135,6 @@ impl DragAndDrop {
|
||||||
/// Returns `true` both during a drag and on the frame the pointer is released
|
/// Returns `true` both during a drag and on the frame the pointer is released
|
||||||
/// (if there is a payload).
|
/// (if there is a payload).
|
||||||
pub fn has_any_payload(ctx: &Context) -> bool {
|
pub fn has_any_payload(ctx: &Context) -> bool {
|
||||||
ctx.data(|data| {
|
ctx.plugin::<Self>().lock().payload.is_some()
|
||||||
let state = data.get_temp::<Self>(Id::NULL);
|
|
||||||
state.is_some_and(|state| state.payload.is_some())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,7 @@
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
mod animation_manager;
|
mod animation_manager;
|
||||||
|
mod atomics;
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
mod context;
|
mod context;
|
||||||
|
|
@ -429,6 +430,7 @@ pub mod os;
|
||||||
mod painter;
|
mod painter;
|
||||||
mod pass_state;
|
mod pass_state;
|
||||||
pub(crate) mod placer;
|
pub(crate) mod placer;
|
||||||
|
mod plugin;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
mod sense;
|
mod sense;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
@ -442,7 +444,6 @@ mod widget_rect;
|
||||||
pub mod widget_text;
|
pub mod widget_text;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
mod atomics;
|
|
||||||
#[cfg(feature = "callstack")]
|
#[cfg(feature = "callstack")]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod callstack;
|
mod callstack;
|
||||||
|
|
@ -501,6 +502,7 @@ pub use self::{
|
||||||
load::SizeHint,
|
load::SizeHint,
|
||||||
memory::{FocusDirection, Memory, Options, Theme, ThemePreference},
|
memory::{FocusDirection, Memory, Options, Theme, ThemePreference},
|
||||||
painter::Painter,
|
painter::Painter,
|
||||||
|
plugin::Plugin,
|
||||||
response::{InnerResponse, Response},
|
response::{InnerResponse, Response},
|
||||||
sense::Sense,
|
sense::Sense,
|
||||||
style::{FontSelection, Spacing, Style, TextStyle, Visuals},
|
style::{FontSelection, Spacing, Style, TextStyle, Visuals},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
use crate::{Context, FullOutput, RawInput};
|
||||||
|
use ahash::HashMap;
|
||||||
|
use epaint::mutex::{Mutex, MutexGuard};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A plugin to extend egui.
|
||||||
|
///
|
||||||
|
/// Add plugins via [`Context::add_plugin`].
|
||||||
|
///
|
||||||
|
/// Plugins should not hold a reference to the [`Context`], since this would create a cycle
|
||||||
|
/// (which would prevent the [`Context`] from being dropped).
|
||||||
|
#[expect(unused_variables)]
|
||||||
|
pub trait Plugin: Send + Sync + std::any::Any + 'static {
|
||||||
|
/// Plugin name.
|
||||||
|
///
|
||||||
|
/// Used when profiling.
|
||||||
|
fn debug_name(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Called once, when the plugin is registered.
|
||||||
|
///
|
||||||
|
/// Useful to e.g. register image loaders.
|
||||||
|
fn setup(&mut self, ctx: &Context) {}
|
||||||
|
|
||||||
|
/// Called at the start of each pass.
|
||||||
|
///
|
||||||
|
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::SidePanel`].
|
||||||
|
fn on_begin_pass(&mut self, ctx: &Context) {}
|
||||||
|
|
||||||
|
/// Called at the end of each pass.
|
||||||
|
///
|
||||||
|
/// Can be used to show ui, e.g. a [`crate::Window`].
|
||||||
|
fn on_end_pass(&mut self, ctx: &Context) {}
|
||||||
|
|
||||||
|
/// Called just before the input is processed.
|
||||||
|
///
|
||||||
|
/// Useful to inspect or modify the input.
|
||||||
|
/// Since this is called outside a pass, don't show ui here.
|
||||||
|
fn input_hook(&mut self, input: &mut RawInput) {}
|
||||||
|
|
||||||
|
/// Called just before the output is passed to the backend.
|
||||||
|
///
|
||||||
|
/// Useful to inspect or modify the output.
|
||||||
|
/// Since this is called outside a pass, don't show ui here.
|
||||||
|
fn output_hook(&mut self, output: &mut FullOutput) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct PluginHandle {
|
||||||
|
plugin: Box<dyn Plugin>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TypedPluginHandle<P: Plugin> {
|
||||||
|
handle: Arc<Mutex<PluginHandle>>,
|
||||||
|
_type: std::marker::PhantomData<P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Plugin> TypedPluginHandle<P> {
|
||||||
|
pub(crate) fn new(handle: Arc<Mutex<PluginHandle>>) -> Self {
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
_type: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lock(&self) -> TypedPluginGuard<'_, P> {
|
||||||
|
TypedPluginGuard {
|
||||||
|
guard: self.handle.lock(),
|
||||||
|
_type: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TypedPluginGuard<'a, P: Plugin> {
|
||||||
|
guard: MutexGuard<'a, PluginHandle>,
|
||||||
|
_type: std::marker::PhantomData<P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Plugin> TypedPluginGuard<'_, P> {}
|
||||||
|
|
||||||
|
impl<P: Plugin> std::ops::Deref for TypedPluginGuard<'_, P> {
|
||||||
|
type Target = P;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.guard.typed_plugin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Plugin> std::ops::DerefMut for TypedPluginGuard<'_, P> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.guard.typed_plugin_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginHandle {
|
||||||
|
pub fn new<P: Plugin>(plugin: P) -> Arc<Mutex<Self>> {
|
||||||
|
Arc::new(Mutex::new(Self {
|
||||||
|
plugin: Box::new(plugin),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plugin_type_id(&self) -> std::any::TypeId {
|
||||||
|
(*self.plugin).type_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dyn_plugin_mut(&mut self) -> &mut dyn Plugin {
|
||||||
|
&mut *self.plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_plugin<P: Plugin + 'static>(&self) -> &P {
|
||||||
|
(&*self.plugin as &dyn std::any::Any)
|
||||||
|
.downcast_ref::<P>()
|
||||||
|
.expect("PluginHandle: plugin is not of the expected type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn typed_plugin_mut<P: Plugin + 'static>(&mut self) -> &mut P {
|
||||||
|
(&mut *self.plugin as &mut dyn std::any::Any)
|
||||||
|
.downcast_mut::<P>()
|
||||||
|
.expect("PluginHandle: plugin is not of the expected type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User-registered plugins.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub(crate) struct Plugins {
|
||||||
|
plugins: HashMap<std::any::TypeId, Arc<Mutex<PluginHandle>>>,
|
||||||
|
plugins_ordered: PluginsOrdered,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub(crate) struct PluginsOrdered(Vec<Arc<Mutex<PluginHandle>>>);
|
||||||
|
|
||||||
|
impl PluginsOrdered {
|
||||||
|
fn for_each_dyn<F>(&self, mut f: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&mut dyn Plugin),
|
||||||
|
{
|
||||||
|
for plugin in &self.0 {
|
||||||
|
let mut plugin = plugin.lock();
|
||||||
|
profiling::scope!("plugin", plugin.dyn_plugin_mut().debug_name());
|
||||||
|
f(plugin.dyn_plugin_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_begin_pass(&self, ctx: &Context) {
|
||||||
|
profiling::scope!("plugins", "on_begin_pass");
|
||||||
|
self.for_each_dyn(|p| {
|
||||||
|
p.on_begin_pass(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_end_pass(&self, ctx: &Context) {
|
||||||
|
profiling::scope!("plugins", "on_end_pass");
|
||||||
|
self.for_each_dyn(|p| {
|
||||||
|
p.on_end_pass(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_input(&self, input: &mut RawInput) {
|
||||||
|
profiling::scope!("plugins", "on_input");
|
||||||
|
self.for_each_dyn(|plugin| {
|
||||||
|
plugin.input_hook(input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_output(&self, output: &mut FullOutput) {
|
||||||
|
profiling::scope!("plugins", "on_output");
|
||||||
|
self.for_each_dyn(|plugin| {
|
||||||
|
plugin.output_hook(output);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugins {
|
||||||
|
pub fn ordered_plugins(&self) -> PluginsOrdered {
|
||||||
|
self.plugins_ordered.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remember to call [`Plugin::setup`] on the plugin after adding it.
|
||||||
|
///
|
||||||
|
/// Will not add the plugin if a plugin of the same type already exists.
|
||||||
|
/// Returns `false` if the plugin was not added, `true` if it was added.
|
||||||
|
pub fn add(&mut self, handle: Arc<Mutex<PluginHandle>>) -> bool {
|
||||||
|
profiling::scope!("plugins", "add");
|
||||||
|
|
||||||
|
let type_id = handle.lock().plugin_type_id();
|
||||||
|
|
||||||
|
if self.plugins.contains_key(&type_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.plugins.insert(type_id, handle.clone());
|
||||||
|
self.plugins_ordered.0.push(handle);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, type_id: std::any::TypeId) -> Option<Arc<Mutex<PluginHandle>>> {
|
||||||
|
self.plugins.get(&type_id).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic event callback.
|
||||||
|
pub type ContextCallback = Arc<dyn Fn(&Context) + Send + Sync>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct CallbackPlugin {
|
||||||
|
pub on_begin_plugins: Vec<(&'static str, ContextCallback)>,
|
||||||
|
pub on_end_plugins: Vec<(&'static str, ContextCallback)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for CallbackPlugin {
|
||||||
|
fn debug_name(&self) -> &'static str {
|
||||||
|
"CallbackPlugins"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||||
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
for (_debug_name, cb) in &self.on_begin_plugins {
|
||||||
|
profiling::scope!(*_debug_name);
|
||||||
|
(cb)(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_end_pass(&mut self, ctx: &Context) {
|
||||||
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
for (_debug_name, cb) in &self.on_end_plugins {
|
||||||
|
profiling::scope!(*_debug_name);
|
||||||
|
(cb)(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,8 @@ use std::sync::Arc;
|
||||||
use emath::TSTransform;
|
use emath::TSTransform;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, CursorIcon, Event, Galley, Id, LayerId, Pos2, Rect, Response, Ui, layers::ShapeIdx,
|
Context, CursorIcon, Event, Galley, Id, LayerId, Plugin, Pos2, Rect, Response, Ui,
|
||||||
text::CCursor, text_selection::CCursorRange,
|
layers::ShapeIdx, text::CCursor, text_selection::CCursorRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
|
@ -123,65 +123,45 @@ impl Default for LabelSelectionState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LabelSelectionState {
|
impl Plugin for LabelSelectionState {
|
||||||
pub(crate) fn register(ctx: &Context) {
|
fn debug_name(&self) -> &'static str {
|
||||||
ctx.on_begin_pass("LabelSelectionState", std::sync::Arc::new(Self::begin_pass));
|
"LabelSelectionState"
|
||||||
ctx.on_end_pass("LabelSelectionState", std::sync::Arc::new(Self::end_pass));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(ctx: &Context) -> Self {
|
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||||
let id = Id::new(ctx.viewport_id());
|
|
||||||
ctx.data(|data| data.get_temp::<Self>(id))
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store(self, ctx: &Context) {
|
|
||||||
let id = Id::new(ctx.viewport_id());
|
|
||||||
ctx.data_mut(|data| {
|
|
||||||
data.insert_temp(id, self);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn begin_pass(ctx: &Context) {
|
|
||||||
let mut state = Self::load(ctx);
|
|
||||||
|
|
||||||
if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
|
if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
|
||||||
// Maybe a new selection is about to begin, but the old one is over:
|
// Maybe a new selection is about to begin, but the old one is over:
|
||||||
// state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected.
|
// state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected.
|
||||||
}
|
}
|
||||||
|
|
||||||
state.selection_bbox_last_frame = state.selection_bbox_this_frame;
|
self.selection_bbox_last_frame = self.selection_bbox_this_frame;
|
||||||
state.selection_bbox_this_frame = Rect::NOTHING;
|
self.selection_bbox_this_frame = Rect::NOTHING;
|
||||||
|
|
||||||
state.any_hovered = false;
|
self.any_hovered = false;
|
||||||
state.has_reached_primary = false;
|
self.has_reached_primary = false;
|
||||||
state.has_reached_secondary = false;
|
self.has_reached_secondary = false;
|
||||||
state.text_to_copy.clear();
|
self.text_to_copy.clear();
|
||||||
state.last_copied_galley_rect = None;
|
self.last_copied_galley_rect = None;
|
||||||
state.painted_selections.clear();
|
self.painted_selections.clear();
|
||||||
|
|
||||||
state.store(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_pass(ctx: &Context) {
|
fn on_end_pass(&mut self, ctx: &Context) {
|
||||||
let mut state = Self::load(ctx);
|
if self.is_dragging {
|
||||||
|
|
||||||
if state.is_dragging {
|
|
||||||
ctx.set_cursor_icon(CursorIcon::Text);
|
ctx.set_cursor_icon(CursorIcon::Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !state.has_reached_primary || !state.has_reached_secondary {
|
if !self.has_reached_primary || !self.has_reached_secondary {
|
||||||
// We didn't see both cursors this frame,
|
// We didn't see both cursors this frame,
|
||||||
// maybe because they are outside the visible area (scrolling),
|
// maybe because they are outside the visible area (scrolling),
|
||||||
// or one disappeared. In either case we will have horrible glitches, so let's just deselect.
|
// or one disappeared. In either case we will have horrible glitches, so let's just deselect.
|
||||||
|
|
||||||
let prev_selection = state.selection.take();
|
let prev_selection = self.selection.take();
|
||||||
if let Some(selection) = prev_selection {
|
if let Some(selection) = prev_selection {
|
||||||
// This was the first frame of glitch, so hide the
|
// This was the first frame of glitch, so hide the
|
||||||
// glitching by removing all painted selections:
|
// glitching by removing all painted selections:
|
||||||
ctx.graphics_mut(|layers| {
|
ctx.graphics_mut(|layers| {
|
||||||
if let Some(list) = layers.get_mut(selection.layer_id) {
|
if let Some(list) = layers.get_mut(selection.layer_id) {
|
||||||
for (shape_idx, row_selections) in state.painted_selections.drain(..) {
|
for (shape_idx, row_selections) in self.painted_selections.drain(..) {
|
||||||
list.mutate_shape(shape_idx, |shape| {
|
list.mutate_shape(shape_idx, |shape| {
|
||||||
if let epaint::Shape::Text(text_shape) = &mut shape.shape {
|
if let epaint::Shape::Text(text_shape) = &mut shape.shape {
|
||||||
let galley = Arc::make_mut(&mut text_shape.galley);
|
let galley = Arc::make_mut(&mut text_shape.galley);
|
||||||
|
|
@ -211,25 +191,25 @@ impl LabelSelectionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let pressed_escape = ctx.input(|i| i.key_pressed(crate::Key::Escape));
|
let pressed_escape = ctx.input(|i| i.key_pressed(crate::Key::Escape));
|
||||||
let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !state.any_hovered;
|
let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
|
||||||
let delected_everything = pressed_escape || clicked_something_else;
|
let delected_everything = pressed_escape || clicked_something_else;
|
||||||
|
|
||||||
if delected_everything {
|
if delected_everything {
|
||||||
state.selection = None;
|
self.selection = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.input(|i| i.pointer.any_released()) {
|
if ctx.input(|i| i.pointer.any_released()) {
|
||||||
state.is_dragging = false;
|
self.is_dragging = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_to_copy = std::mem::take(&mut state.text_to_copy);
|
let text_to_copy = std::mem::take(&mut self.text_to_copy);
|
||||||
if !text_to_copy.is_empty() {
|
if !text_to_copy.is_empty() {
|
||||||
ctx.copy_text(text_to_copy);
|
ctx.copy_text(text_to_copy);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
state.store(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LabelSelectionState {
|
||||||
pub fn has_selection(&self) -> bool {
|
pub fn has_selection(&self) -> bool {
|
||||||
self.selection.is_some()
|
self.selection.is_some()
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +277,8 @@ impl LabelSelectionState {
|
||||||
fallback_color: epaint::Color32,
|
fallback_color: epaint::Color32,
|
||||||
underline: epaint::Stroke,
|
underline: epaint::Stroke,
|
||||||
) {
|
) {
|
||||||
let mut state = Self::load(ui.ctx());
|
let plugin = ui.ctx().plugin::<Self>();
|
||||||
|
let mut state = plugin.lock();
|
||||||
let new_vertex_indices = state.on_label(ui, response, galley_pos, &mut galley);
|
let new_vertex_indices = state.on_label(ui, response, galley_pos, &mut galley);
|
||||||
|
|
||||||
let shape_idx = ui.painter().add(
|
let shape_idx = ui.painter().add(
|
||||||
|
|
@ -309,8 +290,6 @@ impl LabelSelectionState {
|
||||||
.painted_selections
|
.painted_selections
|
||||||
.push((shape_idx, new_vertex_indices));
|
.push((shape_idx, new_vertex_indices));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.store(ui.ctx());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_for(
|
fn cursor_for(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue