Skip to content

Commit

Permalink
Merge pull request 'Add Pointer.clickCount for easy double click, tri…
Browse files Browse the repository at this point in the history
…ple click and multi click support' (#303) from multi-click into main

Reviewed-on: https://git.samerion.com/Samerion/Fluid/pulls/303
CI: https://github.com/Samerion/Fluid/actions/runs/12815250275
  • Loading branch information
ArthaTi authored and Gitea committed Jan 16, 2025
2 parents 099d912 + bfb076d commit 2ee7eda
Show file tree
Hide file tree
Showing 8 changed files with 380 additions and 55 deletions.
9 changes: 7 additions & 2 deletions source/fluid/input_map_chain.d
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class InputMapChain : NodeChain, ActionIO {

/// Find the given event type among ones that were emitted this frame.
/// Safety:
/// The range has to be exhaused immediately.
/// The range has to be exhausted immediately.
/// No input events can be emitted before the range is disposed of, or the range will break.
/// Params:
/// code = Input event code to find.
Expand All @@ -93,14 +93,19 @@ class InputMapChain : NodeChain, ActionIO {

}

/// Detect all input actions that should be emitted as a consequence of the events that occured this frame.
/// Detect all input actions that should be emitted as a consequence of the events that occurred this frame.
/// Clears the current list of events when done.
private void processEvents() @trusted {

scope (exit) _events.clear();

bool handled;

// Test noop event first
foreach (event; findEvents(noopEvent.code)) {
return;
}

// Test all mappings
foreach (layer; map.layers) {

Expand Down
37 changes: 34 additions & 3 deletions source/fluid/io/action.d
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,50 @@ interface ActionIO : IO {

}

/// Create an input event to which `ActionIO` should always have bound to the `CoreAction.frame` input action.
enum Event {
noopEvent,
frameEvent,
}

/// Create an input event which should never activate any input action. For propagation purposes, this event
/// always counts as handled.
///
/// The usual purpose of this event is to prevent input actions from running, assuming the `ActionIO` system's
/// logic stops once an event is handled. For example, `fluid.io.hover.PointerAction` emits this event when
/// it is ordered to run an input action, effectively overriding `ActionIO`'s response.
///
/// See_Also:
/// `frameEvent`
/// Params:
/// isActive = Should the input event be marked as active or not. Defaults to true.
/// Returns:
/// An instance of `Event.noopEvent`.
static InputEvent noopEvent(bool isActive = true) {

const code = InputEventCode(ioID!ActionIO, Event.noopEvent);

return InputEvent(code, isActive);

}

/// Create an input event to which `ActionIO` should always bind to the `CoreAction.frame` input action.
/// Consequently, `ActionIO` always responds with a `CoreAction.frame` input action after processing remaining
/// input actions.
/// input actions. This can be cancelled by emitting a `noopEvent` before the `frameEvent` is handled.
///
/// This can be used by device and input handling I/Os to detect the moment after which all input actions have
/// been processed. This means that it can be used to develop fallback mechanisms like `hoverImpl`
/// and `focusImpl`, which only trigger if no input action has been activated.
///
/// Note that `CoreAction.frame` might, or might not, be emitted if another action event has been emitted during
/// the same frame. `InputMapChain` will only emit `CoreAction.frame` is no other input action has been handled.
///
/// See_Also:
/// `noopEvent`
/// Returns:
/// An instance of `Event.frameEvent`.
static InputEvent frameEvent() {

const code = InputEventCode(ioID!ActionIO, 1);
const code = InputEventCode(ioID!ActionIO, Event.frameEvent);
const isActive = false;

return InputEvent(code, isActive);
Expand Down
68 changes: 37 additions & 31 deletions source/fluid/io/hover.d
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ struct Pointer {
/// Current scroll value. For a mouse, this indicates mouse wheel movement, for other devices like touchpad or
/// touchscreen, this will be translated from its movement.
///
/// This value indicates the distance and direction in window space that the scroll should result in covering.
/// This means that on the X axis negative values move left and positive values move right, while on the Y axis
/// negative values go upwards and positive values go downwards.
/// For example, a scroll value of `(0, 20)` scrolls 20 pixels down vertically, while `(0, -10)` scrolls 10 pixels
/// up.
///
/// While it is possible to read scroll of the `Pointer` data received in an input action handler,
/// it is recommended to implement scroll through `Scrollable.scrollImpl`.
///
Expand All @@ -234,6 +240,16 @@ struct Pointer {
/// It is also possible for a device to perform both horizontal and vertical movement at once.
Vector2 scroll;

/// True if the pointer is not currently pointing, like a finger that stopped touching the screen.
bool isDisabled;

/// Consecutive click counter. A value of 1 represents a single click, 2 is a double click, 3 is a triple click,
/// and so on. The counter should reset after a small delay, or if a distance threshold is crossed.
///
/// This value is usually provided by the system. If unavailable, you can use
/// `fluid.io.preference.MultipleClickCounter` to generate this value from data available to Fluid.
int clickCount;

/// If true, the scroll control is held, like a finger swiping through the screen. This does not apply to mouse
/// wheels.
///
Expand All @@ -245,36 +261,12 @@ struct Pointer {
/// [autoscroll]: (https://chromewebstore.google.com/detail/autoscroll/occjjkgifpmdgodlplnacmkejpdionan)
bool isScrollHeld;

/// True if the pointer is not currently pointing, like a finger that stopped touching the screen.
bool isDisabled;

/// `HoverIO` system controlling the pointer.
private HoverIO _hoverIO;

/// ID of the pointer assigned by the `HoverIO` system.
private int _id;

/// Create a new pointer.
/// Params:
/// device = I/O system representing the device that created the pointer.
/// number = Pointer number as assigned by the device.
/// position = Position of the pointer.
/// isDisabled = If true, disable the node.
this(inout IO device, int number, Vector2 position, Vector2 scroll = Vector2.init, bool isDisabled = false) inout {
this.device = device;
this.number = number;
this.position = position;
this.scroll = scroll;
this.isDisabled = isDisabled;
}

/// Create a new pointer.
/// Params:
/// position = Position of the pointer.
this(Vector2 position) {
this.position = position;
}

/// If the given system is a Hover I/O system, fetch a pointer.
///
/// Given data must be valid; the I/O must be a `HoverIO` instance and the number must be a valid pointer number.
Expand Down Expand Up @@ -348,6 +340,7 @@ struct Pointer {
this.scroll = other.scroll;
this.isScrollHeld = other.isScrollHeld;
this.isDisabled = other.isDisabled;
this.clickCount = other.clickCount;
}

/// Emit an event through the pointer.
Expand Down Expand Up @@ -739,18 +732,18 @@ class PointerAction : TreeAction, Publisher!PointerAction, IO {

/// Run an input action on the currently hovered node, if any.
/// Params:
/// actionID = ID of the action to run.
/// isActive = "Active" status of the action.
/// actionID = ID of the action to run.
/// isActive = "Active" status of the action.
/// Returns:
/// True if the action was handled, false if not.
bool runInputAction(immutable InputActionID actionID, bool isActive = true) {

hoverIO.loadTo(pointer);
auto hoverable = hoverIO.hoverOf(pointer);

// Emit a matching, fake hover event
const code = InputEventCode(ioID!HoverIO, -1);
const event = InputEvent(code, isActive);
// Emit a matching, fake hover event, to inform HoverIO of this
// If HoverIO uses ActionIO, ActionIO should recognize and prioritize this event
const event = ActionIO.noopEvent(isActive);
hoverIO.emitEvent(pointer, event);

// No hoverable
Expand All @@ -772,8 +765,20 @@ class PointerAction : TreeAction, Publisher!PointerAction, IO {

}

/// Shorthand for `runInputAction!(FluidInputAction.press)`
alias press = runInputAction!(FluidInputAction.press);
/// Perform a left click.
/// Params:
/// isActive = Trigger input actions (like a mouse release event) if true, emulate holding if false.
/// clickCount = Set to 2 to emulate a double click, 3 to emulate a triple click, etc.
/// Returns:
/// True if the action was handled, false if not.
bool click(bool isActive = true, int clickCount = 1) {

pointer.clickCount = clickCount;
return runInputAction!(FluidInputAction.press)(isActive);

}

alias press = click;

override void beforeDraw(Node node, Rectangle) {

Expand All @@ -793,6 +798,7 @@ class PointerAction : TreeAction, Publisher!PointerAction, IO {
pointer.isDisabled = true;
pointer.scroll = Vector2();
pointer.isScrollHeld = false;
pointer.clickCount = 0;
hoverIO.loadTo(pointer);

_onInteraction(this);
Expand Down
Loading

0 comments on commit 2ee7eda

Please sign in to comment.