uicore-ts
Version:
UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework tha
441 lines (291 loc) • 12.5 kB
text/typescript
import { UIColor } from "./UIColor"
import { IS, nil, NO, YES } from "./UIObject"
import { UIView, UIViewAddControlEventTargetObject, UIViewBroadcastEvent } from "./UIView"
export class UIBaseButton extends UIView {
static override controlEvent = Object.assign({}, UIView.controlEvent, {
"PrimaryActionTriggered": "PrimaryActionTriggered"
} as const)
override controlEvent = UIBaseButton.controlEvent
override get controlEventTargetAccumulator(): UIViewAddControlEventTargetObject<typeof UIBaseButton> {
return super.controlEventTargetAccumulator as any
}
_selected: boolean = NO
_highlighted: boolean = NO
override _isPointerInside: boolean
_isToggleable: boolean = NO
_hovered?: boolean
_focused?: boolean
constructor(elementID?: string, elementType?: string) {
super(elementID, undefined, elementType)
// Instance variables
this._isPointerInside = NO
const setHovered = () => {
this.hovered = YES
}
this.addTargetForControlEvent(UIView.controlEvent.PointerHover, setHovered)
const setNotHovered = () => {
this.hovered = NO
}
this.addTargetForControlEvents([
UIView.controlEvent.PointerLeave, UIView.controlEvent.PointerCancel, UIView.controlEvent.MultipleTouches
], setNotHovered)
let highlightingTime: number
const setHighlighted = () => {
this.highlighted = YES
highlightingTime = Date.now()
}
this.addTargetForControlEvent(UIView.controlEvent.PointerDown, setHighlighted)
this.addTargetForControlEvent(UIView.controlEvent.PointerEnter, setHighlighted)
const setNotHighlighted = () => {
this.highlighted = NO
}
const setNotHighlightedWithMinimumDuration = () => {
const minimumDurationInMilliseconds = 50
const elapsedTime = Date.now() - highlightingTime
if (minimumDurationInMilliseconds < elapsedTime) {
this.highlighted = NO
}
else {
setTimeout(() => {
this.highlighted = NO
}, minimumDurationInMilliseconds - elapsedTime)
}
}
this.addTargetForControlEvents([
UIView.controlEvent.PointerLeave, UIView.controlEvent.PointerCancel, UIView.controlEvent.MultipleTouches
], setNotHighlighted)
this.addTargetForControlEvent(UIView.controlEvent.PointerUp, setNotHighlightedWithMinimumDuration)
// Enter and Space both activate the button.
// Fire PointerUpInside (which cascades to PrimaryActionTriggered via sendControlEventForKey).
this.addTargetForControlEvent(UIView.controlEvent.EnterDown, () => {
setHighlighted()
setNotHighlightedWithMinimumDuration()
this.sendControlEventForKey(UIView.controlEvent.PointerUpInside, nil)
})
this.addTargetForControlEvent(UIView.controlEvent.SpaceDown, (sender, event) => {
event.preventDefault()
setHighlighted()
setNotHighlightedWithMinimumDuration()
this.sendControlEventForKey(UIView.controlEvent.PointerUpInside, nil)
})
this.addTargetForControlEvent(
UIView.controlEvent.Focus,
(sender: UIView, event: Event) => {
this.focused = YES
}
)
this.addTargetForControlEvent(
UIView.controlEvent.Blur,
(sender: UIView, event: Event) => {
this.focused = NO
}
)
this.pausesPointerEvents = YES
this.tabIndex = 1
this.style.cursor = "pointer"
//this.style.outline = "none";
this.nativeSelectionEnabled = NO
this.addTargetForControlEvents([
UIBaseButton.controlEvent.PrimaryActionTriggered, UIView.controlEvent.PointerUpInside
], () => {
if (this.isToggleable) {
this.toggleSelectedState()
}
})
}
public set hovered(hovered: boolean) {
this._hovered = hovered
this.updateContentForCurrentState()
}
public get hovered(): boolean {
return this._hovered ?? NO
}
public set highlighted(highlighted: boolean) {
this._highlighted = highlighted
this.updateContentForCurrentState()
}
public get highlighted(): boolean {
return this._highlighted
}
public set focused(focused: boolean) {
this._focused = focused
if (focused) {
this.focus()
}
else {
this.blur()
}
this.updateContentForCurrentState()
}
public get focused(): boolean {
return this._focused ?? NO
}
public set selected(selected: boolean) {
this._selected = selected
this.updateContentForCurrentState()
}
public get selected(): boolean {
return this._selected
}
updateContentForCurrentState() {
let updateFunction: Function = this.updateContentForNormalState
if (this.selected && this.highlighted) {
updateFunction = this.updateContentForSelectedAndHighlightedState
}
else if (this.selected) {
updateFunction = this.updateContentForSelectedState
}
else if (this.focused) {
updateFunction = this.updateContentForFocusedState
}
else if (this.highlighted) {
updateFunction = this.updateContentForHighlightedState
}
else if (this.hovered) {
updateFunction = this.updateContentForHoveredState
}
if (!IS(updateFunction)) {
this.backgroundColor = UIColor.nilColor
}
else {
updateFunction.call(this)
}
}
updateContentForNormalState() {
}
updateContentForHoveredState() {
this.updateContentForNormalState()
}
updateContentForFocusedState() {
this.updateContentForHoveredState()
}
updateContentForHighlightedState() {
}
updateContentForSelectedState() {
}
updateContentForSelectedAndHighlightedState() {
this.updateContentForSelectedState()
}
override set enabled(enabled: boolean) {
super.enabled = enabled
this.updateContentForCurrentEnabledState()
}
override get enabled() {
return super.enabled
}
override updateContentForCurrentEnabledState() {
if (this.enabled) {
this.alpha = 1
}
else {
this.alpha = 0.5
}
this.userInteractionEnabled = this.enabled
}
override addStyleClass(styleClassName: string) {
super.addStyleClass(styleClassName)
if (this.styleClassName != styleClassName) {
this.updateContentForCurrentState.call(this)
}
}
override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {
super.didReceiveBroadcastEvent(event)
if (event.name == UIView.broadcastEventName.PageDidScroll || event.name ==
UIView.broadcastEventName.AddedToViewTree) {
const wasHovered = this._hovered
const wasHighlighted = this._highlighted
this._hovered = NO
this._highlighted = NO
if (wasHovered || wasHighlighted) {
this.updateContentForCurrentState()
}
}
}
toggleSelectedState() {
this.selected = !this.selected
}
set isToggleable(isToggleable: boolean) {
this._isToggleable = isToggleable
}
get isToggleable() {
return this._isToggleable
}
override layoutSubviews() {
super.layoutSubviews()
const bounds = this.bounds
}
override sendControlEventForKey(eventKey: string, nativeEvent: Event) {
if (eventKey == UIView.controlEvent.PointerUpInside && !this.highlighted) {
// Do not send the event in this case
//super.sendControlEventForKey(eventKey, nativeEvent);
const asd = 1
}
else {
super.sendControlEventForKey(eventKey, nativeEvent)
if (eventKey == UIView.controlEvent.PointerUpInside) {
super.sendControlEventForKey(UIBaseButton.controlEvent.PrimaryActionTriggered, nativeEvent)
}
}
}
static getEventCoordinatesInDocument(touchOrMouseEvent: any) {
// http://www.quirksmode.org/js/events_properties.html
var posx = 0
var posy = 0
var e = touchOrMouseEvent
if (!e) {
e = window.event
}
if (e.pageX || e.pageY) {
posx = e.pageX
posy = e.pageY
}
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft
posy = e.clientY + document.body.scrollTop
+ document.documentElement.scrollTop
}
// posx and posy contain the mouse position relative to the document
const coordinates = { "x": posx, "y": posy }
return coordinates
}
static getElementPositionInDocument(el: { tagName: string; offsetLeft: number; scrollLeft: number; clientLeft: number; offsetTop: number; scrollTop: number; clientTop: number; offsetParent: any }) {
//https://www.kirupa.com/html5/getting_mouse_click_position.htm
var xPosition = 0
var yPosition = 0
while (el) {
if (el.tagName == "BODY") {
// Coordinates in document are coordinates in body, therefore subtracting the scroll position of the body is not needed
// // deal with browser quirks with body/window/document and page scroll
// var xScrollPos = el.scrollLeft || document.documentElement.scrollLeft;
// var yScrollPos = el.scrollTop || document.documentElement.scrollTop;
//
// xPosition += (el.offsetLeft - xScrollPos + el.clientLeft);
// yPosition += (el.offsetTop - yScrollPos + el.clientTop);
}
else {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft)
yPosition += (el.offsetTop - el.scrollTop + el.clientTop)
}
el = el.offsetParent
}
return {
x: xPosition,
y: yPosition
}
}
static convertCoordinatesFromDocumentToElement(x: number, y: number, element: any) {
const elementPositionInDocument = this.getElementPositionInDocument(element)
const coordinatesInElement = { "x": x - elementPositionInDocument.x, "y": y - elementPositionInDocument.y }
return coordinatesInElement
}
static getEventCoordinatesInElement(touchOrMouseEvent: any, element: any) {
const coordinatesInDocument = this.getEventCoordinatesInDocument(touchOrMouseEvent)
const coordinatesInElement = this.convertCoordinatesFromDocumentToElement(
coordinatesInDocument.x,
coordinatesInDocument.y,
element
)
return coordinatesInElement
}
}