Skip to content

Commit

Permalink
Merge branch 'dev' into feature/scrollbox-expose-scroll-values
Browse files Browse the repository at this point in the history
  • Loading branch information
Zyie committed May 22, 2024
2 parents 98f5561 + 6ca85ef commit 8dfd34a
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 17 deletions.
81 changes: 76 additions & 5 deletions src/FancyButton.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-len */
import { Container, isMobile, NineSliceSprite, ObservablePoint, Rectangle, Texture, Ticker } from 'pixi.js';
import { Group, Tween } from 'tweedle.js';
import { ButtonContainer } from './Button';
Expand Down Expand Up @@ -63,6 +64,8 @@ export type ButtonOptions = ViewsInput & {
iconOffset?: Offset;
defaultTextScale?: Pos | number;
defaultIconScale?: Pos | number;
defaultTextAnchor?: Pos | number;
defaultIconAnchor?: Pos | number;
animations?: StateAnimations;
nineSliceSprite?: [number, number, number, number];
ignoreRefitting?: boolean;
Expand Down Expand Up @@ -144,6 +147,12 @@ export class FancyButton extends ButtonContainer
/** Base icon scaling to take into account when fitting inside the button */
protected _defaultIconScale: Pos = { x: 1, y: 1 };

/** Base text anchor to take into account when fitting and placing inside the button */
protected _defaultTextAnchor: Pos = { x: 0.5, y: 0.5 };

/** Base icon anchor to take into account when fitting and placing inside the button */
protected _defaultIconAnchor: Pos = { x: 0.5, y: 0.5 };

/**
* Creates a button with a lot of tweaks.
* @param {object} options - Button options.
Expand All @@ -162,6 +171,8 @@ export class FancyButton extends ButtonContainer
* when all animations scales will be applied to the inner view.
* @param {number} options.defaultTextScale - Base text scaling to take into account when fitting inside the button.
* @param {number} options.defaultIconScale - Base icon scaling to take into account when fitting inside the button.
* @param {number} options.defaultTextAnchor - Base text anchor to take into account when fitting and placing inside the button.
* @param {number} options.defaultIconAnchor - Base icon anchor to take into account when fitting and placing inside the button.
* @param {number} options.anchor - Anchor point of the button.
* @param {number} options.anchorX - Horizontal anchor point of the button.
* @param {number} options.anchorY - Vertical anchor point of the button.
Expand All @@ -185,6 +196,8 @@ export class FancyButton extends ButtonContainer
iconOffset,
defaultTextScale: textScale,
defaultIconScale: iconScale,
defaultTextAnchor: textAnchor,
defaultIconAnchor: iconAnchor,
scale,
anchor,
anchorX,
Expand All @@ -206,6 +219,8 @@ export class FancyButton extends ButtonContainer
this.iconOffset = iconOffset;
this.defaultTextScale = textScale;
this.defaultIconScale = iconScale;
this.defaultTextAnchor = textAnchor;
this.defaultIconAnchor = iconAnchor;
this.scale.set(scale ?? 1);

if (animations)
Expand Down Expand Up @@ -323,7 +338,6 @@ export class FancyButton extends ButtonContainer
this._defaultTextScale = { x, y };
}

this._views.textView.anchor.set(0);
this.innerView.addChild(this._views.textView);

this.adjustTextView(this.state);
Expand Down Expand Up @@ -395,6 +409,7 @@ export class FancyButton extends ButtonContainer
if (!this.text) return;

const activeView = this.getStateView(this.state);
const { x: anchorX, y: anchorY } = this._defaultTextAnchor;

if (activeView)
{
Expand All @@ -409,7 +424,7 @@ export class FancyButton extends ButtonContainer
this._views.textView.y = activeView.y + (activeView.height / 2);
}

this._views.textView.anchor.set(0.5);
this._views.textView.anchor.set(anchorX, anchorY);

this.setOffset(this._views.textView, state, this.textOffset);
}
Expand Down Expand Up @@ -437,12 +452,24 @@ export class FancyButton extends ButtonContainer
this._views.iconView.scale.set(this._defaultIconScale.x, this._defaultIconScale.y);
}

const { x: anchorX, y: anchorY } = this._defaultIconAnchor;

fitToView(activeView, this._views.iconView, this.padding, false);

(this._views.iconView as Sprite).anchor?.set(0);
if ('anchor' in this._views.iconView)
{
(this._views.iconView.anchor as ObservablePoint).set(anchorX, anchorY);
}
else
{
this._views.iconView.pivot.set(
anchorX * (this._views.iconView.width / this._views.iconView.scale.x),
anchorY * (this._views.iconView.height / this._views.iconView.scale.y)
);
}

this._views.iconView.x = activeView.x + (activeView.width / 2) - (this._views.iconView.width / 2);
this._views.iconView.y = activeView.y + (activeView.height / 2) - (this._views.iconView.height / 2);
this._views.iconView.x = activeView.x + (activeView.width / 2);
this._views.iconView.y = activeView.y + (activeView.height / 2);

this.setOffset(this._views.iconView, state, this.iconOffset);
}
Expand Down Expand Up @@ -888,6 +915,50 @@ export class FancyButton extends ButtonContainer
return this.defaultIconScale;
}

/**
* Sets the base anchor for the text view to take into account when fitting and placing inside the button.
* @param {Pos | number} anchor - base anchor of the text view.
*/
set defaultTextAnchor(anchor: Pos | number)
{
if (anchor === undefined) return;
// Apply to the options so that the manual anchor is prioritized.
this.options.defaultTextAnchor = anchor;
const isNumber = typeof anchor === 'number';

this._defaultTextAnchor.x = isNumber ? anchor : anchor.x ?? 1;
this._defaultTextAnchor.y = isNumber ? anchor : anchor.y ?? 1;
this.adjustTextView(this.state);
}

/** Returns the text view base anchor. */
get defaultTextAnchor(): Pos
{
return this.defaultTextAnchor;
}

/**
* Sets the base anchor for the icon view to take into account when fitting and placing inside the button.
* @param {Pos | number} anchor - base anchor of the icon view.
*/
set defaultIconAnchor(anchor: Pos | number)
{
if (anchor === undefined) return;
// Apply to the options so that the manual anchor is prioritized.
this.options.defaultIconAnchor = anchor;
const isNumber = typeof anchor === 'number';

this._defaultIconAnchor.x = isNumber ? anchor : anchor.x ?? 1;
this._defaultIconAnchor.y = isNumber ? anchor : anchor.y ?? 1;
this.adjustIconView(this.state);
}

/** Returns the icon view base anchor. */
get defaultIconAnchor(): Pos
{
return this.defaultIconAnchor;
}

/**
* Sets width of a FancyButtons state views.
* If nineSliceSprite is set, then width will be set to nineSliceSprites of a views.
Expand Down
57 changes: 50 additions & 7 deletions src/ScrollBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PointData,
Ticker,
} from 'pixi.js';
import { Signal } from 'typed-signals';
import { List } from './List';
import { Trackpad } from './utils/trackpad/Trackpad';

Expand All @@ -26,8 +27,17 @@ export type ScrollBoxOptions = {
dragTrashHold?: number;
globalScroll?: boolean;
shiftScroll?: boolean;
proximityRange?: number;
proximityDebounce?: number;
disableProximityCheck?: boolean;
} & Omit<ListOptions, 'children'>;

type ProximityEventData = {
item: Container;
index: number;
inRange: boolean;
};

/**
* Scrollable view, for arranging lists of Pixi container-based elements.
*
Expand Down Expand Up @@ -77,6 +87,13 @@ export class ScrollBox extends Container
protected dragStarTouchPoint: Point;
protected isOver = false;

protected proximityRange: number;
protected proximityStatusCache: boolean[] = [];
protected lastScrollX!: number | null;
protected lastScrollY!: number | null;
protected proximityCheckFrameCounter = 0;
public onProximityChange = new Signal<(data: ProximityEventData) => void>();

/**
* @param options
* @param {number} options.background - background color of the ScrollBox.
Expand Down Expand Up @@ -129,6 +146,8 @@ export class ScrollBox extends Container
this.__width = options.width | this.background.width;
this.__height = options.height | this.background.height;

this.proximityRange = options.proximityRange ?? 0;

if (!this.list)
{
this.list = new List();
Expand Down Expand Up @@ -183,6 +202,7 @@ export class ScrollBox extends Container
/** Remove all items from a scrollable list. */
removeItems()
{
this.proximityStatusCache.length = 0;
this.list.removeChildren();
}

Expand All @@ -208,6 +228,7 @@ export class ScrollBox extends Container
child.eventMode = 'static';

this.list.addChild(child);
this.proximityStatusCache.push(false);

if (!this.options.disableDynamicRendering)
{
Expand All @@ -227,15 +248,16 @@ export class ScrollBox extends Container
removeItem(itemID: number)
{
this.list.removeItem(itemID);

this.proximityStatusCache.splice(itemID, 1);
this.resize();
}

/**
* Checks if the item is visible or scrolled out of the visible part of the view.* Adds an item to a scrollable list.
* @param {Container} item - item to check.
* @param padding - proximity padding to consider the item visible.
*/
isItemVisible(item: Container): boolean
isItemVisible(item: Container, padding = 0): boolean
{
const isVertical = this.options.type === 'vertical' || !this.options.type;
let isVisible = false;
Expand All @@ -245,10 +267,7 @@ export class ScrollBox extends Container
{
const posY = item.y + list.y;

if (
posY + item.height + this.list.bottomPadding >= 0
&& posY - this.list.topPadding <= this.options.height
)
if (posY + item.height >= -padding && posY <= this.options.height + padding)
{
isVisible = true;
}
Expand All @@ -257,7 +276,7 @@ export class ScrollBox extends Container
{
const posX = item.x + list.x;

if (posX + item.width >= 0 && posX <= this.options.width)
if (posX + item.width >= -padding && posX <= this.options.width + padding)
{
isVisible = true;
}
Expand Down Expand Up @@ -775,6 +794,30 @@ export class ScrollBox extends Container
{
this.list[type] = this._trackpad[type];
}

if (!this.options.disableProximityCheck && (
this._trackpad.x !== this.lastScrollX || this._trackpad.y !== this.lastScrollY
))
{
this.proximityCheckFrameCounter++;
if (this.proximityCheckFrameCounter >= (this.options.proximityDebounce ?? 10))
{
this.items.forEach((item, index) =>
{
const inRange = this.isItemVisible(item, this.proximityRange);
const wasInRange = this.proximityStatusCache[index];

if (inRange !== wasInRange)
{
this.proximityStatusCache[index] = inRange;
this.onProximityChange.emit({ item, index, inRange });
}
});
this.lastScrollX = this._trackpad.x;
this.lastScrollY = this._trackpad.y;
this.proximityCheckFrameCounter = 0;
}
}
}

/**
Expand Down
10 changes: 7 additions & 3 deletions src/stories/fancyButton/FancyButtonBitmapText.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const args = {
textOffsetX: 0,
textOffsetY: -7,
defaultTextScale: 0.99,
defaultTextAnchorX: 0.5,
defaultTextAnchorY: 0.5,
anchorX: 0.5,
anchorY: 0.5,
animationDuration: 100,
Expand All @@ -31,9 +33,11 @@ export const UsingSpriteAndBitmapText: StoryFn<typeof args> = (
textOffsetX,
textOffsetY,
defaultTextScale,
defaultTextAnchorX,
defaultTextAnchorY,
anchorX,
anchorY,
animationDuration
animationDuration,
},
context
) =>
Expand All @@ -54,14 +58,13 @@ export const UsingSpriteAndBitmapText: StoryFn<typeof args> = (
name: 'TitleFont',
style: {
...defaultTextStyle,

fill: textColor || defaultTextStyle.fill,
},
});

const title = new BitmapText({
text,
style: { fontFamily: 'TitleFont' },
style: { fontFamily: 'TitleFont', fontSize: defaultTextStyle.fontSize },
});

// Component usage !!!
Expand All @@ -74,6 +77,7 @@ export const UsingSpriteAndBitmapText: StoryFn<typeof args> = (
padding,
textOffset: { x: textOffsetX, y: textOffsetY },
defaultTextScale,
defaultTextAnchor: { x: defaultTextAnchorX, y: defaultTextAnchorY },
animations: {
hover: {
props: {
Expand Down
10 changes: 10 additions & 0 deletions src/stories/fancyButton/FancyButtonDynamicUpdate.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const args = {
textColor: '#FFFFFF',
defaultTextScale: 0.99,
defaultIconScale: 0.2,
defaultTextAnchorX: 0.5,
defaultTextAnchorY: 0.5,
defaultIconAnchorX: 0.5,
defaultIconAnchorY: 0.5,
padding: 11,
anchorX: 0.5,
anchorY: 0.5,
Expand All @@ -25,6 +29,10 @@ export const DynamicUpdate: StoryFn<typeof args> = ({
textColor,
defaultTextScale,
defaultIconScale,
defaultTextAnchorX,
defaultTextAnchorY,
defaultIconAnchorX,
defaultIconAnchorY,
disabled,
onPress,
padding,
Expand All @@ -50,6 +58,7 @@ export const DynamicUpdate: StoryFn<typeof args> = ({

button.iconView = Sprite.from(icon);
button.defaultIconScale = defaultIconScale;
button.defaultIconAnchor = { x: defaultIconAnchorX, y: defaultIconAnchorY };
button.iconOffset = { x: -100, y: -7 };

button.textView = new Text({
Expand All @@ -59,6 +68,7 @@ export const DynamicUpdate: StoryFn<typeof args> = ({
}
});
button.defaultTextScale = defaultTextScale;
button.defaultTextAnchor = { x: defaultTextAnchorX, y: defaultTextAnchorY };
button.textOffset = { x: 30, y: -7 };

button.padding = padding;
Expand Down
Loading

0 comments on commit 8dfd34a

Please sign in to comment.