Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PageIndicator component #35

Merged
merged 4 commits into from
Sep 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions @zapp/core/src/ZappInterface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ViewManager } from './renderer/ViewManager.js'

let zappInstance: ZappInterface

export enum Platform {
Expand All @@ -23,17 +25,17 @@ export abstract class ZappInterface {
public abstract readonly screenShape: ScreenShape
}

export const Zapp: ZappInterface = {
export const Zapp = {
startLoop() {
zappInstance.startLoop()
},
stopLoop() {
zappInstance.stopLoop()
},
setValue(key, value) {
setValue(key: string, value: unknown) {
zappInstance.setValue(key, value)
},
getValue(key) {
getValue(key: string): unknown {
return zappInstance.getValue(key)
},
get platform() {
Expand All @@ -42,4 +44,10 @@ export const Zapp: ZappInterface = {
get screenShape() {
return zappInstance.screenShape
},
get screenWidth() {
return ViewManager.screenWidth
},
get screenHeight() {
return ViewManager.screenHeight
},
}
157 changes: 157 additions & 0 deletions @zapp/ui/src/PageIndicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
Arrangement,
Column,
ColumnConfig,
ConfigBuilder,
ConfigType,
remember,
Row,
RowConfig,
ScreenShape,
Stack,
StackAlignment,
StackConfig,
Zapp,
} from '@zapp/core'
import { Theme } from './Theme.js'

const DOT_SIZE = px(16)
const DOT_SPACING = px(2)
const SPACE_FOR_DOT = DOT_SIZE + DOT_SPACING * 2

interface PageIndicatorConfigType extends ConfigType {
numberOfPages: number
currentPage: number
curved: boolean
curveRadius: number
horizontal: boolean
}

export class PageIndicatorConfigBuilder extends ConfigBuilder {
protected config: PageIndicatorConfigType

constructor(id: string) {
super(id)
this.config.numberOfPages = 1
this.config.currentPage = 0
this.config.curveRadius = Zapp.screenWidth / 2
this.config.curved = Zapp.screenShape === ScreenShape.Round
this.config.horizontal = true
}

numberOfPages(numberOfPages: number) {
this.config.numberOfPages = numberOfPages
return this
}

currentPage(currentPage: number) {
this.config.currentPage = currentPage
return this
}

curveRadius(curveRadius: number) {
this.config.curveRadius = curveRadius
return this
}

curved(curved: boolean) {
this.config.curved = curved
return this
}

horizontal(horizontal: boolean) {
this.config.horizontal = horizontal
return this
}

build(): PageIndicatorConfigType {
return this.config
}
}

export function PageIndicatorConfig(id: string): PageIndicatorConfigBuilder {
return new PageIndicatorConfigBuilder(id)
}

function calculateDotOffset(index: number, config: PageIndicatorConfigType, radius?: number) {
let oX = 0
let oY = 0

if (config.curved) {
let distanceFromCenter = index - Math.floor(config.numberOfPages / 2)
if (config.numberOfPages % 2 === 0) {
distanceFromCenter += 0.5
}

const r = (radius ?? config.curveRadius) - DOT_SIZE / 2
const degPerDot = (180 * SPACE_FOR_DOT) / (Math.PI * r) // 360 * size / (2 * pi * r)
const angularPosition = degPerDot * distanceFromCenter * 0.0174532925
const relativePosition = distanceFromCenter * SPACE_FOR_DOT

if (config.horizontal) {
oY -= r * (1 - Math.cos(angularPosition))
oX -= relativePosition - Math.sin(angularPosition) * r
} else {
oX -= r * (1 - Math.cos(angularPosition))
oY -= relativePosition - Math.sin(angularPosition) * r
}
}

return { x: oX, y: oY }
}

function renderDot(index: number, rawConfig: PageIndicatorConfigType) {
const { x: oX, y: oY } = calculateDotOffset(index, rawConfig)

// call remembers in top-level to minimize amount of nodes created, this means that the number
// of pages should not change while the indicator is visible
const offsetX = remember(oX)
const offsetY = remember(oY)

Stack(
StackConfig(`${rawConfig.id}#dot#${index}`)
.background(index === rawConfig.currentPage ? Theme.primary : Theme.primaryContainer)
.width(DOT_SIZE)
.height(DOT_SIZE)
.cornerRadius(DOT_SIZE / 2)
.offset(offsetX.value, offsetY.value)
)
}

function renderDots(rawConfig: PageIndicatorConfigType) {
for (let i = 0; i < rawConfig.numberOfPages; i++) {
renderDot(i, rawConfig)
}
}

export function PageIndicator(config: PageIndicatorConfigBuilder) {
const rawConfig = config.build()

Stack(
StackConfig(`${rawConfig.id}#wrapper`)
.fillSize()
.positionAbsolutely(true)
.alignment(rawConfig.horizontal ? StackAlignment.BottomCenter : StackAlignment.CenterEnd),
() => {
if (rawConfig.horizontal) {
Row(
RowConfig(`${rawConfig.id}#dotsContainer`)
.width(rawConfig.numberOfPages * SPACE_FOR_DOT)
.arrangement(Arrangement.SpaceAround),
() => {
renderDots(rawConfig)
}
)
} else {
Column(
ColumnConfig(`${rawConfig.id}#dotsContainer`)
.height(rawConfig.numberOfPages * SPACE_FOR_DOT)
.arrangement(Arrangement.SpaceAround),
() => {
renderDots(rawConfig)
}
)
}
}
)
}
10 changes: 10 additions & 0 deletions @zapp/ui/src/Text.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { TextConfigBuilder, BareText } from '@zapp/core'
import { Theme } from './Theme.js'

const nextColors: number[] = []
const nextFontSizes: number[] = []

const DEFAULT_TEXT_SIZE = 32

export function Text(config: TextConfigBuilder, text: string) {
const rawConfig = config.build()

Expand All @@ -16,6 +19,13 @@ export function Text(config: TextConfigBuilder, text: string) {
config.textSize(nextFontSize)
}

if (rawConfig.textSize === undefined) {
config.textSize(DEFAULT_TEXT_SIZE)
}
if (rawConfig.textColor === undefined) {
config.textColor(Theme.onBackground)
}

BareText(config, text)
}

Expand Down
1 change: 1 addition & 0 deletions @zapp/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { Theme, ThemeInterface, setTheme } from './Theme.js'
export { ActivityIndicator } from './ActivityIndicator.js'
export { Text } from './Text.js'
export { Button, ButtonConfig, ButtonStyle } from './Button.js'
export { PageIndicator, PageIndicatorConfig } from './PageIndicator.js'
100 changes: 79 additions & 21 deletions watch-test/page/pager.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,95 @@ import {
Navigator,
registerGestureEventHandler,
} from '@zapp/core'
import { Button, ButtonConfig, PageIndicator, PageIndicatorConfig, Text } from '@zapp/ui'

ScreenPager(ScreenPagerConfig('screen', 3).startingPage(1), () => {
function renderPageIndicator(current) {
Stack(Config('indicatorContainer'), () => {
const page = rememberCurrentPage()
if (page.value === current) {
PageIndicator(PageIndicatorConfig('indicator').numberOfPages(5).currentPage(page.value))
}
})
}

ScreenPager(ScreenPagerConfig('screen', 5).startingPage(2), () => {
PagerEntry(Config('page1'), () => {
Stack(StackConfig('a'), () => {
const page = rememberCurrentPage()
console.log(page.value)
Column(
ColumnConfig('col1')
.fillSize()
.background(0xff0000)
.onPointerUp((e) => {
if (e.y < 100) {
page.value = 2
}
})
Column(ColumnConfig('page1Column').fillSize().alignment(Alignment.Center).arrangement(Arrangement.Center), () => {
Button(
ButtonConfig('page1Button').onPress(() => {
Navigator.navigate('page/page3')
}),
() => {
Text(TextConfig('page1ButtonText'), 'Do stuff')
}
)
Text(TextConfig('page1Text').textSize(80), '1')
})

renderPageIndicator(0)
})

PagerEntry(Config('page2'), () => {
Column(ColumnConfig('col2').fillSize().background(0x00ff00))
Column(ColumnConfig('page2Column').fillSize().alignment(Alignment.Center).arrangement(Arrangement.Center), () => {
Button(
ButtonConfig('page2Button').onPress(() => {
Navigator.navigate('page/page3')
}),
() => {
Text(TextConfig('page2ButtonText'), 'Do stuff')
}
)
Text(TextConfig('page2Text').textSize(80), '2')
})

renderPageIndicator(1)
})

PagerEntry(Config('page3'), () => {
Column(
ColumnConfig('col3')
.fillSize()
.onPointerUp((e) => {
Column(ColumnConfig('page3Column').fillSize().alignment(Alignment.Center).arrangement(Arrangement.Center), () => {
Button(
ButtonConfig('page3Button').onPress(() => {
Navigator.navigate('page/page3')
})
.background(0x0000ff)
)
}),
() => {
Text(TextConfig('page3ButtonText'), 'Do stuff')
}
)
Text(TextConfig('page3Text').textSize(80), '3')
})

renderPageIndicator(2)
})

PagerEntry(Config('page4'), () => {
Column(ColumnConfig('page4Column').fillSize().alignment(Alignment.Center).arrangement(Arrangement.Center), () => {
Button(
ButtonConfig('page4Button').onPress(() => {
Navigator.navigate('page/page3')
}),
() => {
Text(TextConfig('page4ButtonText'), 'Do stuff')
}
)
Text(TextConfig('page4Text').textSize(80), '4')
})

renderPageIndicator(3)
})

PagerEntry(Config('page5'), () => {
Column(ColumnConfig('page5Column').fillSize().alignment(Alignment.Center).arrangement(Arrangement.Center), () => {
Button(
ButtonConfig('page5Button').onPress(() => {
Navigator.navigate('page/page3')
}),
() => {
Text(TextConfig('page5ButtonText'), 'Do stuff')
}
)
Text(TextConfig('page5Text').textSize(80), '5')
})

renderPageIndicator(4)
})
})
Loading