Skip to content

Commit

Permalink
Implement #89
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthaTi committed Feb 26, 2024
1 parent e0d1fea commit 3aeb037
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 45 deletions.
6 changes: 3 additions & 3 deletions source/fluid/actions.d
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,17 @@ class ScrollIntoViewAction : TreeAction {

// Reached a scroll node
// TODO What if the container isn't an ancestor
else if (auto scrollable = cast(AnyScrollable) node) {
else if (auto scrollable = cast(FluidScrollable) node) {

// Perform the scroll
childBox = scrollable.shallowScrollTo(node, viewport, paddingBox, childBox);
childBox = scrollable.shallowScrollTo(target, paddingBox, childBox);

// Aligning to top, make sure the child aligns with the parent
if (alignToTop && childBox.y > paddingBox.y) {

const offset = childBox.y - paddingBox.y;

scrollable.setScroll(scrollable.scroll + cast(size_t) offset);
scrollable.scroll = scrollable.scroll + cast(size_t) offset;

}

Expand Down
27 changes: 27 additions & 0 deletions source/fluid/input.d
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,33 @@ interface FluidFocusable : FluidHoverable {

}

/// An interface to be implemented by nodes that accept scroll input.
interface FluidScrollable {

/// Returns true if the node can react to given scroll.
///
/// Should return false if the given scroll has no effect, either because it scroll on an unsupported axis, or
/// because the axis is currently maxed out.
bool canScroll(Vector2 value) const;

/// React to scroll wheel input.
void scrollImpl(Vector2 value);

/// Scroll to given child node.
/// Params:
/// child = Child to scroll to.
/// parentBox = Outer box of this node (the scrollable).
/// childBox = Outer box of the child node (the target).
Rectangle shallowScrollTo(const Node child, Rectangle parentBox, Rectangle childBox);

/// Get current scroll value.
ptrdiff_t scroll() const;

/// Set scroll value.
ptrdiff_t scroll(ptrdiff_t value);

}

/// Represents a general input node.
///
/// Styles: $(UL
Expand Down
27 changes: 25 additions & 2 deletions source/fluid/node.d
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ abstract class Node : Styleable {
// Clear mouse hover if LMB is up
if (!isLMBHeld) tree.hover = null;

// Clear scroll
tree.scroll = null;

// Clear focus info
tree.focusDirection = FocusDirection(tree.focusBox);
tree.focusBox = Rectangle(float.nan);
Expand Down Expand Up @@ -667,6 +670,9 @@ abstract class Node : Styleable {
// Note: pressed, not released; released activates input events, pressed activates focus
const mousePressed = tree.io.isPressed(MouseButton.left);

// Update scroll input
if (tree.scroll) tree.scroll.scrollImpl(io.scroll);

// Mouse is hovering an input node
// Note that nodes will remain in tree.hover if LMB is pressed to prevent "hover slipping" — actions should
// only trigger if the button was both pressed and released on the node.
Expand Down Expand Up @@ -934,8 +940,25 @@ abstract class Node : Styleable {
const heldElsewhere = !tree.io.isPressed(MouseButton.left)
&& isLMBHeld;

// Update global hover unless mouse is being held down or mouse focus is disabled for this node
if (isHovered && !heldElsewhere && !ignoreMouse) tree.hover = this;
// Check for hover, unless ignored by this node
if (isHovered && !ignoreMouse) {

// Set global hover as long as the mouse isn't held down
if (!heldElsewhere) tree.hover = this;

// Update scroll
if (auto scrollable = cast(FluidScrollable) this) {

// Only if scrolling is possible
if (scrollable.canScroll(io.scroll)) {

tree.scroll = scrollable;

}

}

}

assert(
only(size.tupleof).all!isFinite,
Expand Down
71 changes: 42 additions & 29 deletions source/fluid/scroll.d
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ alias hscrollable(alias T) = simpleConstructor!(ApplyRight!(ScrollFrame, "true")
/// Implement scrolling for the given node.
///
/// This only supports scrolling in one axis.
class Scrollable(T : Node, string horizontalExpression) : T, AnyScrollable {
class Scrollable(T : Node, string horizontalExpression) : T, FluidScrollable {

mixin DefineStyles;

Expand All @@ -68,8 +68,24 @@ class Scrollable(T : Node, string horizontalExpression) : T, AnyScrollable {
}

/// Distance the node is scrolled by.
@property
ref inout(size_t) scroll() inout { return scrollBar.position; }
ref inout(ptrdiff_t) scroll() inout {

return scrollBar.position;

}

ptrdiff_t scroll() const {

return scrollBar.position;

}

ptrdiff_t scroll(ptrdiff_t value) {

setScroll(value);
return value;

}

/// Check if the underlying node is horizontal.
private bool isHorizontal() const {
Expand Down Expand Up @@ -106,8 +122,14 @@ class Scrollable(T : Node, string horizontalExpression) : T, AnyScrollable {

}

Rectangle shallowScrollTo(const Node child, Vector2, Rectangle parentBox, Rectangle childBox) {

return shallowScrollTo(child, parentBox, childBox);

}

/// Scroll to the given node.
Rectangle shallowScrollTo(const Node, Vector2, Rectangle parentBox, Rectangle childBox) {
Rectangle shallowScrollTo(const Node, Rectangle parentBox, Rectangle childBox) {

struct Position {

Expand Down Expand Up @@ -208,20 +230,9 @@ class Scrollable(T : Node, string horizontalExpression) : T, AnyScrollable {

override void drawImpl(Rectangle outer, Rectangle inner) {

// Note: Mouse input detection is primitive, awaiting #13 and #14 to help better identify when should the mouse
// affect this frame.

// This node doesn't use InputNode because it doesn't take focus, and we don't want to cause related
// accessibility issues. It can function perfectly without it, or at least until above note gets fixed.
// Then, a "FluidHoverable" interface could possibly become a thing.

// TODO Is the above still true?

scrollBar.horizontal = isHorizontal;

auto scrollBarRect = outer;

if (isHovered) inputImpl();
scrollBar.horizontal = isHorizontal;

// Scroll the given rectangle horizontally
if (isHorizontal) {
Expand Down Expand Up @@ -275,28 +286,30 @@ class Scrollable(T : Node, string horizontalExpression) : T, AnyScrollable {

}

/// Implementation of mouse input
private void inputImpl() @trusted {
bool canScroll(Vector2 valueVec) const {

// TODO do this via input actions somehow https://git.samerion.com/Samerion/Fluid/issues/89
const speed = scrollBar.scrollSpeed;
const value = isHorizontal
? io.scroll.x
: io.scroll.y;
? valueVec.x
: valueVec.y;
const move = speed * value;
// io.deltaTime is irrelevant here

// TODO NO ptrdiff_t
scrollBar.setScroll(scroll.to!ptrdiff_t + move.to!ptrdiff_t);
return move.to!ptrdiff_t != 0;

}

}
void scrollImpl(Vector2 valueVec) {

interface AnyScrollable {
const speed = scrollBar.scrollSpeed;
const value = isHorizontal
? valueVec.x
: valueVec.y;
const move = speed * value;
// io.deltaTime is irrelevant here

void setScroll(ptrdiff_t value);
ref inout(size_t) scroll() inout;
Rectangle shallowScrollTo(const Node, Vector2, Rectangle parentBox, Rectangle childBox);
// TODO NO ptrdiff_t
scrollBar.setScroll(scroll.to!ptrdiff_t + move.to!ptrdiff_t);

}

}
22 changes: 11 additions & 11 deletions source/fluid/scroll_input.d
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ class ScrollInput : InputNode!Node {
bool horizontal;

/// Amount of pixels the page is scrolled down.
size_t position;
ptrdiff_t position;

/// Available space to scroll.
///
/// Note: visible box size, and therefore scrollbar handle length, are determined from the space occupied by the
/// scrollbar.
size_t availableSpace;
ptrdiff_t availableSpace;

/// Width of the scrollbar.
size_t width = 10;
ptrdiff_t width = 10;

}

Expand Down Expand Up @@ -98,7 +98,7 @@ class ScrollInput : InputNode!Node {
Vector2 grabPosition;

/// Start position of the mouse at the beginning of the grab.
size_t startPosition;
ptrdiff_t startPosition;

}

Expand All @@ -118,21 +118,21 @@ class ScrollInput : InputNode!Node {
/// Set the scroll to a value clamped between start and end. Doesn't trigger the `changed` event.
void setScroll(ptrdiff_t value) {

position = cast(size_t) value.clamp(0, scrollMax);
position = cast(ptrdiff_t) value.clamp(0, scrollMax);

}

/// Ditto
void setScroll(float value) {

position = cast(size_t) value.clamp(0, scrollMax);
position = cast(ptrdiff_t) value.clamp(0, scrollMax);

}

/// Get the maximum value this container can be scrolled to. Requires at least one draw.
size_t scrollMax() const {
ptrdiff_t scrollMax() const {

return cast(size_t) max(0, availableSpace - pageLength);
return cast(ptrdiff_t) max(0, availableSpace - pageLength);

}

Expand Down Expand Up @@ -294,7 +294,7 @@ class ScrollInput : InputNode!Node {
? &isDown!(FluidInputAction.scrollLeft)
: &isDown!(FluidInputAction.scrollUp);

const speed = cast(size_t) actionScrollSpeed;
const speed = cast(ptrdiff_t) actionScrollSpeed;
const change
= isPlus(tree) ? +speed
: isMinus(tree) ? -speed
Expand All @@ -320,9 +320,9 @@ class ScrollInput : InputNode!Node {
}

/// Scroll page length used for `pageUp` and `pageDown` navigation.
protected size_t scrollPageLength() const {
protected ptrdiff_t scrollPageLength() const {

return cast(size_t) (scrollbarLength * 3/4);
return cast(ptrdiff_t) (scrollbarLength * 3/4);

}

Expand Down
3 changes: 3 additions & 0 deletions source/fluid/tree.d
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ struct LayoutTree {
/// `FluidFocusable.focus()` instead and let the node set the value on its own.
FluidFocusable focus;

/// Deepest hovered scrollable node.
FluidScrollable scroll;

/// Focus direction data.
FocusDirection focusDirection;

Expand Down

0 comments on commit 3aeb037

Please sign in to comment.