This commit is contained in:
pengfei.zhou
2019-12-21 21:37:51 +08:00
parent b5cb29ca03
commit 9b5fbedfca
47 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Panel } from '../ui/panel'
import { View } from '../ui/view'
export interface Driver {
/**
* Create and destory page
*/
createPage(): Panel
destoryPage(): Panel
/**
* Page lifecycle
*/
dispatchOnCreate(): void
dispatchOnDestory(): void
dispatchOnShow(): void
dispatchOnHidden(): void
/**
* Page render
*/
dispatchBuild(): View
}
export interface Responser {
constructor(): void
respond(action: string, extra: any): void
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Panel } from "../ui/panel"
import { takeLet } from "../pattern/candies"
import { BridgeContext } from "../runtime/global"
/**
* Only supports x,y,width,height,corner(just for four corners),rotation,bgColor,
* @param panel @see Panel
*/
export function animate(context: BridgeContext) {
const entity = context.entity
if (entity instanceof Panel) {
let panel = entity
return (args: {
animations: () => void,
duration: number,
}) => {
return takeLet(panel.context.animate)(it => {
return it.submit().then(() => {
args.animations()
return takeLet(panel.getRootView())(root => {
if (root.isDirty()) {
const model = root.toModel();
(model as any).duration = args.duration
const ret = it.animateRender(model)
root.clean()
return ret
}
for (let v of panel.allHeadViews()) {
if (v.isDirty()) {
const model = v.toModel()
const ret = it.animateRender(model)
it.clean()
return ret
}
}
throw new Error('Cannot find any animated elements')
})
})
}) as Promise<any>
}
} else {
return (args: {
animations: () => void,
duration: number,
}) => {
return Promise.reject(`Cannot find panel in Context:${context.id}`)
}
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './modal'
export * from './navbar'
export * from './navigator'
export * from './network'
export * from './storage'
export * from './popover'
export * from './animate'

View File

@@ -0,0 +1,61 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BridgeContext } from "../runtime/global"
import { Gravity } from "../util/gravity"
export function modal(context: BridgeContext) {
return {
toast: (msg: string, gravity: Gravity = Gravity.Bottom) => {
context.modal.toast({
msg,
gravity: gravity.toModel(),
})
},
alert: (arg: string | {
title: string,
msg: string,
okLabel?: string,
}) => {
if (typeof arg === 'string') {
return context.modal.alert({ msg: arg })
} else {
return context.modal.alert(arg)
}
},
confirm: (arg: string | {
title: string,
msg: string,
okLabel?: string,
cancelLabel?: string,
}) => {
if (typeof arg === 'string') {
return context.modal.confirm({ msg: arg })
} else {
return context.modal.confirm(arg)
}
},
prompt: (arg: {
title?: string,
msg?: string,
okLabel?: string,
cancelLabel?: string,
text?: string,
defaultText?: string,
}) => {
return context.modal.prompt(arg) as Promise<string>
},
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BridgeContext } from "../runtime/global"
import { Panel } from "../ui/panel"
import { Color } from "../util/color"
export function navbar(context: BridgeContext) {
const entity = context.entity
let panel: Panel | undefined = undefined
if (entity instanceof Panel) {
panel = entity
}
return {
isHidden: () => {
return context.navbar.isHidden() as Promise<boolean>
},
setHidden: (hidden: boolean) => {
return context.navbar.setHidden({
hidden,
})
},
setTitle: (title: string) => {
return context.navbar.setTitle({
title,
})
},
setBgColor: (color: Color) => {
return context.navbar.setBgColor({
color: color.toModel(),
})
},
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BridgeContext } from "../runtime/global"
export function navigator(context: BridgeContext) {
return {
push: (scheme: string, config?: {
alias?: string,
animated?: boolean,
extra?: object,
}) => {
if (config && config.extra) {
(config as any).extra = JSON.stringify(config.extra)
}
return context.navigator.push({
scheme, config
})
},
pop: (animated = true) => {
return context.navigator.pop({ animated })
},
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BridgeContext } from "../runtime/global"
export interface IRequest {
// `url` is the server URL that will be used for the request
url?: string,
// `method` is the request method to be used when making the request
method?: "get" | "post" | "put" | "delete",
// `headers` are custom headers to be sent
headers?: { [index: string]: string }
// `params` are the URL parameters to be sent with the request
// Must be a plain object or a URLSearchParams object
params?: { [index: string]: string }
// `data` is the data to be sent as the request body
// Only applicable for request methods 'PUT', 'POST', and 'PATCH'
data?: object | string
// `timeout` specifies the number of milliseconds before the request times out.
// If the request takes longer than `timeout`, the request will be aborted.
timeout?: number, // default is `0` (no timeout)
}
export interface IResponse {
// `data` is the response that was provided by the server
data: any,
// `status` is the HTTP status code from the server response
status: number,
// `headers` the headers that the server responded with
// All header names are lower cased
headers?: { [index: string]: string },
}
function transformRequest(request: IRequest) {
let url = request.url || ""
if (request.params !== undefined) {
const queryStrings = []
for (let key in request.params) {
queryStrings.push(`${key}=${encodeURIComponent(request.params[key])}`)
}
request.url = `${request.url}${url.indexOf('?') >= 0 ? '&' : '?'}${queryStrings.join('&')}`
}
if (typeof request.data === 'object') {
request.data = JSON.stringify(request.data)
}
return request
}
export function network(context: BridgeContext) {
return {
request: (config: IRequest) => {
return context.network.request(transformRequest(config)) as Promise<IResponse>
},
get: (url: string, config?: IRequest) => {
let finalConfig = config
if (finalConfig === undefined) {
finalConfig = {}
}
finalConfig.url = url
finalConfig.method = "get"
return context.network.request(transformRequest(finalConfig)) as Promise<IResponse>
},
post: (url: string, data?: object | string, config?: IRequest) => {
let finalConfig = config
if (finalConfig === undefined) {
finalConfig = {}
}
finalConfig.url = url
finalConfig.method = "post"
if (data !== undefined) {
finalConfig.data = data
}
return context.network.request(transformRequest(finalConfig)) as Promise<IResponse>
},
put: (url: string, data?: object | string, config?: IRequest) => {
let finalConfig = config
if (finalConfig === undefined) {
finalConfig = {}
}
finalConfig.url = url
finalConfig.method = "put"
if (data !== undefined) {
finalConfig.data = data
}
return context.network.request(transformRequest(finalConfig)) as Promise<IResponse>
},
delete: (url: string, data?: object | string, config?: IRequest) => {
let finalConfig = config
if (finalConfig === undefined) {
finalConfig = {}
}
finalConfig.url = url
finalConfig.method = "delete"
return context.network.request(transformRequest(finalConfig)) as Promise<IResponse>
},
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BridgeContext } from "../runtime/global"
import { View } from "../ui/view"
import { Panel } from "../ui/panel"
export function popover(context: BridgeContext) {
const entity = context.entity
let panel: Panel | undefined = undefined
if (entity instanceof Panel) {
panel = entity
}
return {
show: (view: View) => {
if (panel) {
panel.addHeadView(view)
}
return context.popover.show(view.toModel())
},
dismiss: (view: View | undefined = undefined) => {
if (panel) {
if (view) {
panel.removeHeadView(view)
} else {
panel.clearHeadViews()
}
}
return context.popover.dismiss(view ? { id: view.viewId } : undefined)
},
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BridgeContext } from "../runtime/global"
export function storage(context: BridgeContext) {
return {
setItem: (key: string, value: string, zone?: string) => {
return context.storage.setItem({ key, value, zone })
},
getItem: (key: string, zone?: string) => {
return context.storage.getItem({ key, zone }) as Promise<string>
},
remove: (key: string, zone?: string) => {
return context.storage.remove({ key, zone })
},
clear: (zone: string) => {
return context.storage.clear({ zone })
},
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function take<T>(target: T) {
return (block: (p: T) => void) => {
block(target)
}
}
export function takeNonNull<T, R>(target?: T) {
return (block: (p: T) => R) => {
if (target !== undefined) {
return block(target)
}
}
}
export function takeNull<T, R>(target?: T) {
return (block: () => R) => {
if (target === undefined) {
return block()
}
}
}
export function takeLet<T, R>(target: T) {
return (block: (p: T) => R | undefined) => {
return block(target)
}
}
export function takeAlso<T>(target: T) {
return (block: (p: T) => void) => {
block(target)
return target
}
}
export function takeIf<T>(target: T) {
return (predicate: (t: T) => boolean) => {
return predicate(target) ? target : undefined
}
}
export function takeUnless<T>(target: T) {
return (predicate: (t: T) => boolean) => {
return predicate(target) ? undefined : target
}
}
export function repeat(action: (count: number) => void) {
return (times: number) => {
for (let i = 0; i < times; i++) {
action(i)
}
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './candies'
export * from './provider'
export * from './mvvm'

View File

@@ -0,0 +1,78 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Group } from "../ui/view"
import { Panel } from "../ui/panel"
export abstract class ViewHolder {
abstract build(root: Group): void
}
export type Setter<M> = (state: M) => void
export abstract class ViewModel<M extends Object, V extends ViewHolder> {
private state: M
private viewHolder: V
constructor(obj: M, v: V) {
this.state = obj
this.viewHolder = v
}
getState() {
return this.state
}
updateState(setter: Setter<M>) {
setter(this.state)
this.onBind(this.state, this.viewHolder)
}
attach(view: Group) {
this.viewHolder.build(view)
this.onAttached(this.state, this.viewHolder)
this.onBind(this.state, this.viewHolder)
}
abstract onAttached(state: M, vh: V): void
abstract onBind(state: M, vh: V): void
}
export type ViewModelClass<M, V extends ViewHolder> = new (m: M, v: V) => ViewModel<M, V>
export type ViewHolderClass<V> = new () => V
export abstract class VMPanel<M extends Object, V extends ViewHolder> extends Panel {
private vm?: ViewModel<M, V>
private vh?: V
abstract getViewModelClass(): ViewModelClass<M, V>
abstract getState(): M
abstract getViewHolderClass(): ViewHolderClass<V>
getViewModel() {
return this.vm
}
build(root: Group): void {
this.vh = new (this.getViewHolderClass())
this.vm = new (this.getViewModelClass())(this.getState(), this.vh)
this.vm.attach(root)
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type Observer<T> = (v: T) => void
export type Updater<T> = (v: T) => T
export interface IObservable<T> {
addObserver(observer: Observer<T | undefined>): void
removeObserver(observer: Observer<T | undefined>): void
update(updater: Updater<T | undefined>): void
}
export class Observable<M> implements IObservable<M>{
private provider: IProvider
private clz: { new(...args: any[]): M }
private observers: Set<Observer<M | undefined>> = new Set
constructor(provider: IProvider, clz: { new(...args: any[]): M }) {
this.provider = provider
this.clz = clz
}
addObserver(observer: Observer<M | undefined>): void {
this.observers.add(observer)
}
removeObserver(observer: Observer<M | undefined>): void {
this.observers.delete(observer)
}
update(updater: Updater<M | undefined>): void {
const oldV = this.provider.acquire(this.clz)
const newV = updater(oldV)
if (newV !== undefined) {
this.provider.provide(newV)
}
for (let observer of this.observers) {
observer(newV)
}
}
}
export interface IProvider {
provide(obj: Object): void
acquire<T>(clz: { new(...args: any[]): T }): T | undefined
remove<T>(clz: { new(...args: any[]): T }): void
clear(): void
observe<T>(clz: { new(...args: any[]): T }): Observable<T>
}
export class Provider implements IProvider {
private provision: Map<Function, Object> = new Map
private observableMap: Map<Function, Observable<any>> = new Map
provide(obj: Object) {
this.provision.set(obj.constructor, obj)
}
acquire<T>(clz: { new(...args: any[]): T }): T | undefined {
const ret = this.provision.get(clz)
return ret as T | undefined
}
remove<T>(clz: new (...args: any[]) => T): void {
this.provision.delete(clz)
}
clear(): void {
this.provision.clear()
}
observe<T>(clz: new (...args: any[]) => T): Observable<T> {
let observable = this.observableMap.get(clz)
if (observable === undefined) {
observable = new Observable(this, clz)
this.observableMap.set(clz, observable)
}
return observable
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from 'reflect-metadata'
export type BridgeContext = { [index: string]: { [index: string]: (args?: any) => Promise<any> } }
declare global {
const context: BridgeContext
const Environment: {
platform: "Android" | "iOS" | "Qt",
platformVersion: string,
appName: string,
appVersion: string,
libVersion: string,
screenWidth: number,
screenHeight: number,
}
function Entry(constructor: { new(...args: any[]): {} }): any
}
export { }

View File

@@ -0,0 +1,359 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uniqueId } from "../util/uniqueId";
import { loge } from "../util/log";
import "reflect-metadata"
/**
* ``` TypeScript
* // load script in global scope
* Reflect.apply(
* function(doric,context,Entry,require){
* //Script content
* REG()
* },doric.jsObtainContext(id),[
* undefined,
* doric.jsObtainContext(id),
* doric.jsObtainEntry(id),
* doric.__require__,
* ])
* // load module in global scope
* Reflect.apply(doric.jsRegisterModule,this,[
* moduleName,
* Reflect.apply(function(__module){
* (function(module,exports,require){
* //module content
* })(__module,__module.exports,doric.__require__);
* return __module.exports
* },this,[{exports:{}}])
* ])
*
* ```
*/
declare function nativeRequire(moduleName: string): boolean
declare function nativeBridge(contextId: string, namespace: string, method: string, callbackId?: string, args?: any): boolean
declare function nativeSetTimer(timerId: number, interval: number, repeat: boolean): void
declare function nativeClearTimer(timerId: number): void
function hookBeforeNativeCall(context?: Context) {
if (context) {
Reflect.defineMetadata('__doric_context__', context, global)
context.hookBeforeNativeCall()
}
}
function hookAfterNativeCall(context?: Context) {
if (context) {
context.hookAfterNativeCall()
}
}
function getContext(): Context | undefined {
return Reflect.getMetadata('__doric_context__', global)
}
function setContext(context?: Context) {
Reflect.defineMetadata('__doric_context__', context, global)
}
export function jsCallResolve(contextId: string, callbackId: string, args?: any) {
const context = gContexts.get(contextId)
if (context === undefined) {
loge(`Cannot find context for context id:${contextId}`)
return
}
const callback = context.callbacks.get(callbackId)
if (callback === undefined) {
loge(`Cannot find call for context id:${contextId},callback id:${callbackId}`)
return
}
const argumentsList: any = []
for (let i = 2; i < arguments.length; i++) {
argumentsList.push(arguments[i])
}
hookBeforeNativeCall(context)
Reflect.apply(callback.resolve, context, argumentsList)
hookAfterNativeCall(context)
}
export function jsCallReject(contextId: string, callbackId: string, args?: any) {
const context = gContexts.get(contextId)
if (context === undefined) {
loge(`Cannot find context for context id:${contextId}`)
return
}
const callback = context.callbacks.get(callbackId)
if (callback === undefined) {
loge(`Cannot find call for context id:${contextId},callback id:${callbackId}`)
return
}
const argumentsList: any = []
for (let i = 2; i < arguments.length; i++) {
argumentsList.push(arguments[i])
}
hookBeforeNativeCall(context)
Reflect.apply(callback.reject, context.entity, argumentsList)
hookAfterNativeCall(context)
}
export class Context {
entity: any
id: string
callbacks: Map<string, { resolve: Function, reject: Function }> = new Map
hookBeforeNativeCall() {
if (this.entity && Reflect.has(this.entity, 'hookBeforeNativeCall')) {
Reflect.apply(Reflect.get(this.entity, 'hookBeforeNativeCall'), this.entity, [])
}
}
hookAfterNativeCall() {
if (this.entity && Reflect.has(this.entity, 'hookAfterNativeCall')) {
Reflect.apply(Reflect.get(this.entity, 'hookAfterNativeCall'), this.entity, [])
}
}
constructor(id: string) {
this.id = id
return new Proxy(this, {
get: (target, p: any) => {
if (Reflect.has(target, p)) {
return Reflect.get(target, p)
} else {
const namespace = p
return new Proxy({}, {
get: (target, p: any) => {
if (Reflect.has(target, p)) {
return Reflect.get(target, p)
} else {
const context = this
return function () {
const args = []
args.push(namespace)
args.push(p)
for (let arg of arguments) {
args.push(arg)
}
return Reflect.apply(context.callNative, context, args)
}
}
}
})
}
}
})
}
callNative(namespace: string, method: string, args?: any): Promise<any> {
const callbackId = uniqueId('callback')
nativeBridge(this.id, namespace, method, callbackId, args)
return new Promise((resolve, reject) => {
this.callbacks.set(callbackId, {
resolve,
reject,
})
})
}
register(instance: Object) {
this.entity = instance
}
}
const gContexts: Map<string, Context> = new Map
const gModules: Map<string, any> = new Map
export function jsObtainContext(id: string) {
if (gContexts.has(id)) {
const context = gContexts.get(id)
setContext(context)
return context
} else {
const context: Context = new Context(id)
gContexts.set(id, context)
setContext(context)
return context
}
}
export function jsReleaseContext(id: string) {
const context = gContexts.get(id)
const args = arguments
if (context) {
timerInfos.forEach((v, k) => {
if (v.context === context) {
if (global.nativeClearTimer === undefined) {
return Reflect.apply(_clearTimeout, undefined, args)
}
timerInfos.delete(k)
nativeClearTimer(k)
}
})
}
gContexts.delete(id)
}
export function __require__(name: string): any {
if (gModules.has(name)) {
return gModules.get(name)
} else {
if (nativeRequire(name)) {
return gModules.get(name)
} else {
return undefined
}
}
}
export function jsRegisterModule(name: string, moduleObject: any) {
gModules.set(name, moduleObject)
}
export function jsCallEntityMethod(contextId: string, methodName: string, args?: any) {
const context = gContexts.get(contextId)
if (context === undefined) {
loge(`Cannot find context for context id:${contextId}`)
return
}
if (context.entity === undefined) {
loge(`Cannot find holder for context id:${contextId}`)
return
}
if (Reflect.has(context.entity, methodName)) {
const argumentsList: any = []
for (let i = 2; i < arguments.length; i++) {
argumentsList.push(arguments[i])
}
hookBeforeNativeCall(context)
const ret = Reflect.apply(Reflect.get(context.entity, methodName), context.entity, argumentsList)
hookAfterNativeCall(context)
return ret
} else {
loge(`Cannot find method for context id:${contextId},method name is:${methodName}`)
}
}
export function jsObtainEntry(contextId: string) {
const context = jsObtainContext(contextId)
return <T extends { new(...args: any[]): {} }>(constructor: T) => {
const ret = class extends constructor {
context = context
}
if (context) {
context.register(new ret)
}
return ret
}
}
const global = Function('return this')()
let __timerId__ = 0
const timerInfos: Map<number, { callback: () => void, context?: Context }> = new Map
const _setTimeout = global.setTimeout
const _setInterval = global.setInterval
const _clearTimeout = global.clearTimeout
const _clearInterval = global.clearInterval
const doricSetTimeout = function (handler: Function, timeout?: number | undefined, ...args: any[]) {
if (global.nativeSetTimer === undefined) {
return Reflect.apply(_setTimeout, undefined, arguments)
}
const id = __timerId__++
timerInfos.set(id, {
callback: () => {
Reflect.apply(handler, undefined, args)
timerInfos.delete(id)
},
context: getContext(),
})
nativeSetTimer(id, timeout || 0, false)
return id
}
const doricSetInterval = function (handler: Function, timeout?: number | undefined, ...args: any[]) {
if (global.nativeSetTimer === undefined) {
return Reflect.apply(_setInterval, undefined, arguments)
}
const id = __timerId__++
timerInfos.set(id, {
callback: () => {
Reflect.apply(handler, undefined, args)
},
context: getContext(),
})
nativeSetTimer(id, timeout || 0, true)
return id
}
const doricClearTimeout = function (timerId: number) {
if (global.nativeClearTimer === undefined) {
return Reflect.apply(_clearTimeout, undefined, arguments)
}
timerInfos.delete(timerId)
nativeClearTimer(timerId)
}
const doricClearInterval = function (timerId: number) {
if (global.nativeClearTimer === undefined) {
return Reflect.apply(_clearInterval, undefined, arguments)
}
timerInfos.delete(timerId)
nativeClearTimer(timerId)
}
if (!global.setTimeout) {
global.setTimeout = doricSetTimeout
} else {
global.doricSetTimeout = doricSetTimeout
}
if (!global.setInterval) {
global.setInterval = doricSetInterval
} else {
global.doricSetInterval = doricSetInterval
}
if (!global.clearTimeout) {
global.clearTimeout = doricClearTimeout
} else {
global.doricClearTimeout = doricClearTimeout
}
if (!global.clearInterval) {
global.clearInterval = doricClearInterval
} else {
global.doricClearInterval = doricClearInterval
}
export function jsCallbackTimer(timerId: number) {
const timerInfo = timerInfos.get(timerId)
if (timerInfo === undefined) {
return
}
if (timerInfo.callback instanceof Function) {
hookBeforeNativeCall(timerInfo.context)
Reflect.apply(timerInfo.callback, timerInfo.context, [])
hookAfterNativeCall(timerInfo.context)
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Modeling, Model } from "../util/types"
export type AnimatedKey = "translationX" | "translationY" | "scaleX" | "scaleY" | "rotation" | "pivotX" | "pivotY"
export enum RepeatMode {
RESTART = 1,
REVERSE = 2,
}
export interface IAnimation extends Modeling {
duration: number
delay?: number
}
export interface Changeable {
fromValue: number
toValue: number
key: AnimatedKey
repeatCount?: number
repeatMode?: RepeatMode
}
export enum FillMode {
/**
* The receiver is removed from the presentation when the animation is completed.
*/
Removed = 0,
/**
* The receiver remains visible in its final state when the animation is completed.
*/
Forward = 0x1,
/**
* The receiver clamps values before zero to zero when the animation is completed.
*/
Backward = 0x2,
/**
* The receiver clamps values at both ends of the objects time space
*/
Both = 0x3,
}
export enum TimingFunction {
/**
* The system default timing function. Use this function to ensure that the timing of your animations matches that of most system animations.
*/
Default = 0,
/**
* Linear pacing, which causes an animation to occur evenly over its duration.
*/
Linear,
/**
* Ease-in pacing, which causes an animation to begin slowly and then speed up as it progresses.
*/
EaseIn,
/**
* Ease-out pacing, which causes an animation to begin quickly and then slow as it progresses.
*/
EaseOut,
/**
* Ease-in-ease-out pacing, which causes an animation to begin slowly, accelerate through the middle of its duration, and then slow again before completing.
*/
EaseInEaseOut,
}
abstract class Animation implements IAnimation {
changeables: Map<AnimatedKey, Changeable> = new Map
duration = 0
repeatCount?: number
repeatMode?: RepeatMode
delay?: number
fillMode = FillMode.Forward
timingFunction?: TimingFunction
toModel() {
const changeables = []
for (let e of this.changeables.values()) {
changeables.push({
key: e.key,
fromValue: e.fromValue,
toValue: e.toValue,
})
}
return {
type: this.constructor.name,
delay: this.delay,
duration: this.duration,
changeables,
repeatCount: this.repeatCount,
repeatMode: this.repeatMode,
fillMode: this.fillMode,
timingFunction: this.timingFunction
}
}
}
export class ScaleAnimation extends Animation {
private scaleXChangeable: Changeable = {
key: "scaleX",
fromValue: 1,
toValue: 1,
}
private scaleYChangeable: Changeable = {
key: "scaleY",
fromValue: 1,
toValue: 1,
}
constructor() {
super()
this.changeables.set("scaleX", this.scaleXChangeable)
this.changeables.set("scaleY", this.scaleYChangeable)
}
set fromScaleX(v: number) {
this.scaleXChangeable.fromValue = v
}
get fromScaleX() {
return this.scaleXChangeable.fromValue
}
set toScaleX(v: number) {
this.scaleXChangeable.toValue = v
}
get toScaleX() {
return this.scaleXChangeable.toValue
}
set fromScaleY(v: number) {
this.scaleYChangeable.fromValue = v
}
get fromScaleY() {
return this.scaleYChangeable.fromValue
}
set toScaleY(v: number) {
this.scaleYChangeable.toValue = v
}
get toScaleY() {
return this.scaleYChangeable.toValue
}
}
export class TranslationAnimation extends Animation {
private translationXChangeable: Changeable = {
key: "translationX",
fromValue: 1,
toValue: 1,
}
private translationYChangeable: Changeable = {
key: "translationY",
fromValue: 1,
toValue: 1,
}
constructor() {
super()
this.changeables.set("translationX", this.translationXChangeable)
this.changeables.set("translationY", this.translationYChangeable)
}
set fromTranslationX(v: number) {
this.translationXChangeable.fromValue = v
}
get fromTranslationX() {
return this.translationXChangeable.fromValue
}
set toTranslationX(v: number) {
this.translationXChangeable.toValue = v
}
get toTranslationX() {
return this.translationXChangeable.toValue
}
set fromTranslationY(v: number) {
this.translationYChangeable.fromValue = v
}
get fromTranslationY() {
return this.translationYChangeable.fromValue
}
set toTranslationY(v: number) {
this.translationYChangeable.toValue = v
}
get toTranslationY() {
return this.translationYChangeable.toValue
}
}
export class RotationAnimation extends Animation {
private rotationChaneable: Changeable = {
key: "rotation",
fromValue: 1,
toValue: 1,
}
constructor() {
super()
this.changeables.set("rotation", this.rotationChaneable)
}
set fromRotation(v: number) {
this.rotationChaneable.fromValue = v
}
get fromRotation() {
return this.rotationChaneable.fromValue
}
set toRotation(v: number) {
this.rotationChaneable.toValue = v
}
get toRotation() {
return this.rotationChaneable.toValue
}
}
export class AnimationSet implements IAnimation {
private animations: IAnimation[] = []
_duration = 0
delay?: number
addAnimation(anim: IAnimation) {
this.animations.push(anim)
}
get duration() {
return this._duration
}
set duration(v: number) {
this._duration = v
this.animations.forEach(e => e.duration = v)
}
toModel() {
return {
animations: this.animations.map(e => {
return e.toModel()
}) as Model,
delay: this.delay,
}
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './view'
export * from './panel'
export * from './animation'

169
doric-js/src/ui/panel.ts Normal file
View File

@@ -0,0 +1,169 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../runtime/global'
import { View, Group } from "./view"
import { loge } from '../util/log'
import { Model } from '../util/types'
import { Root } from '../widget/layouts'
export function NativeCall(target: Panel, propertyKey: string, descriptor: PropertyDescriptor) {
const originVal = descriptor.value
descriptor.value = function () {
const ret = Reflect.apply(originVal, this, arguments)
return ret
}
return descriptor
}
type Frame = { width: number, height: number }
declare function nativeEmpty(): void
export abstract class Panel {
context?: any
onCreate() { }
onDestroy() { }
onShow() { }
onHidden() { }
abstract build(rootView: Group): void
private __data__?: object
private __root__ = new Root
private headviews: Map<string, View> = new Map
addHeadView(v: View) {
this.headviews.set(v.viewId, v)
}
allHeadViews() {
return this.headviews.values()
}
removeHeadView(v: View | string) {
if (v instanceof View) {
this.headviews.delete(v.viewId)
} else {
this.headviews.delete(v)
}
}
clearHeadViews() {
this.headviews.clear()
}
getRootView() {
return this.__root__
}
getInitData() {
return this.__data__
}
@NativeCall
private __init__(frame: Frame, data?: string) {
if (data) {
this.__data__ = JSON.parse(data)
}
this.__root__.width = frame.width
this.__root__.height = frame.height
this.__root__.children.length = 0
this.build(this.__root__)
}
@NativeCall
private __onCreate__() {
this.onCreate()
}
@NativeCall
private __onDestroy__() {
this.onDestroy()
}
@NativeCall
private __onShow__() {
this.onShow()
}
@NativeCall
private __onHidden__(): void {
this.onHidden()
}
@NativeCall
private __build__() {
this.build(this.__root__)
}
@NativeCall
private __response__(viewIds: string[], callbackId: string) {
const v = this.retrospectView(viewIds)
if (v === undefined) {
loge(`Cannot find view for ${viewIds}`)
} else {
const argumentsList: any = [callbackId]
for (let i = 2; i < arguments.length; i++) {
argumentsList.push(arguments[i])
}
return Reflect.apply(v.responseCallback, v, argumentsList)
}
}
private retrospectView(ids: string[]): View | undefined {
return ids.reduce((acc: View | undefined, cur) => {
if (acc === undefined) {
if (cur === this.__root__.viewId) {
return this.__root__
}
return this.headviews.get(cur)
} else {
if (Reflect.has(acc, "subviewById")) {
return Reflect.apply(Reflect.get(acc, "subviewById"), acc, [cur])
}
return acc
}
}, undefined)
}
private nativeRender(model: Model) {
if (this.context) {
this.context.shader.render(model)
}
}
private hookBeforeNativeCall() {
this.__root__.clean()
for (let v of this.headviews.values()) {
v.clean()
}
}
private hookAfterNativeCall() {
//Here insert a native call to ensure the promise is resolved done.
nativeEmpty()
if (this.__root__.isDirty()) {
const model = this.__root__.toModel()
this.nativeRender(model)
}
for (let v of this.headviews.values()) {
if (v.isDirty()) {
const model = v.toModel()
this.nativeRender(model)
}
}
}
}

401
doric-js/src/ui/view.ts Normal file
View File

@@ -0,0 +1,401 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Color, GradientColor } from "../util/color"
import { Modeling, Model, obj2Model } from "../util/types";
import { uniqueId } from "../util/uniqueId";
import { loge } from "../util/log";
import { BridgeContext } from "../runtime/global";
import { LayoutConfig } from '../util/layoutconfig'
import { IAnimation } from "./animation";
export function Property(target: Object, propKey: string) {
Reflect.defineMetadata(propKey, true, target)
}
export interface IView {
width?: number
height?: number
backgroundColor?: Color | GradientColor
corners?: number | { leftTop?: number; rightTop?: number; leftBottom?: number; rightBottom?: number }
border?: { width: number; color: Color; }
shadow?: { color: Color; opacity: number; radius: number; offsetX: number; offsetY: number }
/**
* float [0,..1]
*/
alpha?: number
hidden?: boolean
padding?: {
left?: number,
right?: number,
top?: number,
bottom?: number,
}
layoutConfig?: LayoutConfig
onClick?: Function
identifier?: string
/**++++++++++transform++++++++++*/
translationX?: number
translationY?: number
scaleX?: number
scaleY?: number
/**
* float [0,..1]
*/
pivotX?: number
/**
* float [0,..1]
*/
pivotY?: number
/**
* rotation*PI
*/
rotation?: number
/**----------transform----------*/
}
export abstract class View implements Modeling, IView {
@Property
width: number = 0
@Property
height: number = 0
@Property
x: number = 0
@Property
y: number = 0
@Property
backgroundColor?: Color | GradientColor
@Property
corners?: number | { leftTop?: number; rightTop?: number; leftBottom?: number; rightBottom?: number }
@Property
border?: { width: number; color: Color; }
@Property
shadow?: { color: Color; opacity: number; radius: number; offsetX: number; offsetY: number }
@Property
alpha?: number
@Property
hidden?: boolean
@Property
viewId = uniqueId('ViewId')
@Property
padding?: {
left?: number,
right?: number,
top?: number,
bottom?: number,
}
@Property
layoutConfig?: LayoutConfig
@Property
onClick?: Function
superview?: Superview
callbacks: Map<String, Function> = new Map
private callback2Id(f: Function) {
const id = uniqueId('Function')
this.callbacks.set(id, f)
return id
}
private id2Callback(id: string) {
let f = this.callbacks.get(id)
if (f === undefined) {
f = Reflect.get(this, id) as Function
}
return f
}
constructor() {
return new Proxy(this, {
get: (target, p, receiver) => {
return Reflect.get(target, p, receiver)
},
set: (target, p, v, receiver) => {
const oldV = Reflect.get(target, p, receiver)
const ret = Reflect.set(target, p, v, receiver)
if (Reflect.getMetadata(p, target) && oldV !== v) {
receiver.onPropertyChanged(p.toString(), oldV, v)
}
return ret
}
})
}
/** Anchor start*/
get left() {
return this.x
}
set left(v: number) {
this.x = v
}
get right() {
return this.x + this.width
}
set right(v: number) {
this.x = v - this.width
}
get top() {
return this.y
}
set top(v: number) {
this.y = v
}
get bottom() {
return this.y + this.height
}
set bottom(v: number) {
this.y = v - this.height
}
get centerX() {
return this.x + this.width / 2
}
get centerY() {
return this.y + this.height / 2
}
set centerX(v: number) {
this.x = v - this.width / 2
}
set centerY(v: number) {
this.y = v - this.height / 2
}
/** Anchor end*/
private __dirty_props__: { [index: string]: Model | undefined } = {}
get dirtyProps() {
return this.__dirty_props__
}
nativeViewModel = {
id: this.viewId,
type: this.constructor.name,
props: this.__dirty_props__,
}
onPropertyChanged(propKey: string, oldV: Model, newV: Model): void {
if (newV instanceof Function) {
newV = this.callback2Id(newV)
} else {
newV = obj2Model(newV)
}
this.__dirty_props__[propKey] = newV
}
clean() {
for (const key in this.__dirty_props__) {
if (Reflect.has(this.__dirty_props__, key)) {
Reflect.deleteProperty(this.__dirty_props__, key)
}
}
}
isDirty() {
return Reflect.ownKeys(this.__dirty_props__).length !== 0
}
responseCallback(id: string, ...args: any) {
const f = this.id2Callback(id)
if (f instanceof Function) {
const argumentsList: any = []
for (let i = 1; i < arguments.length; i++) {
argumentsList.push(arguments[i])
}
return Reflect.apply(f, this, argumentsList)
} else {
loge(`Cannot find callback:${id} for ${JSON.stringify(this.toModel())}`)
}
}
toModel() {
return this.nativeViewModel
}
let(block: (it: this) => void) {
block(this)
}
also(block: (it: this) => void) {
block(this)
return this
}
apply(config: IView) {
for (let key in config) {
Reflect.set(this, key, Reflect.get(config, key, config), this)
}
return this
}
in(group: Group) {
group.addChild(this)
return this
}
nativeChannel(context: any, name: string) {
let thisView: View | undefined = this
return function (args: any = undefined) {
const func = context.shader.command
const viewIds = []
while (thisView != undefined) {
viewIds.push(thisView.viewId)
thisView = thisView.superview
}
const params = {
viewIds: viewIds.reverse(),
name,
args,
}
return Reflect.apply(func, undefined, [params]) as Promise<any>
}
}
getWidth(context: BridgeContext) {
return this.nativeChannel(context, 'getWidth')() as Promise<number>
}
getHeight(context: BridgeContext) {
return this.nativeChannel(context, 'getHeight')() as Promise<number>
}
getLocationOnScreen(context: BridgeContext) {
return this.nativeChannel(context, "getLocationOnScreen")() as Promise<{ x: number, y: number }>
}
/**++++++++++transform++++++++++*/
@Property
translationX?: number
@Property
translationY?: number
@Property
scaleX?: number
@Property
scaleY?: number
@Property
pivotX?: number
@Property
pivotY?: number
@Property
rotation?: number
/**----------transform----------*/
doAnimation(context: BridgeContext, animation: IAnimation) {
return this.nativeChannel(context, "doAnimation")(animation.toModel()).then((args) => {
for (let key in args) {
Reflect.set(this, key, Reflect.get(args, key, args), this)
Reflect.deleteProperty(this.__dirty_props__, key)
}
})
}
}
export abstract class Superview extends View {
subviewById(id: string): View | undefined {
for (let v of this.allSubviews()) {
if (v.viewId === id) {
return v
}
}
}
abstract allSubviews(): Iterable<View>
isDirty() {
if (super.isDirty()) {
return true
} else {
for (const v of this.allSubviews()) {
if (v.isDirty()) {
return true
}
}
}
return false
}
clean() {
for (let v of this.allSubviews()) {
v.clean()
}
super.clean()
}
toModel() {
const subviews = []
for (let v of this.allSubviews()) {
if (v != undefined) {
v.superview = this
if (v.isDirty()) {
subviews.push(v.toModel())
}
}
}
this.dirtyProps.subviews = subviews
return super.toModel()
}
}
export abstract class Group extends Superview {
readonly children: View[] = new Proxy([], {
set: (target, index, value) => {
const ret = Reflect.set(target, index, value)
// Let getDirty return true
this.dirtyProps.children = this.children.map(e => e.viewId)
return ret
}
})
allSubviews() {
return this.children
}
addChild(view: View) {
this.children.push(view)
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Modeling } from "./types";
/**
* Store color as format AARRGGBB or RRGGBB
*/
export class Color implements Modeling {
static BLACK = new Color(0xFF000000)
static DKGRAY = new Color(0xFF444444)
static GRAY = new Color(0xFF888888)
static LTGRAY = new Color(0xFFCCCCCC)
static WHITE = new Color(0xFFFFFFFF)
static RED = new Color(0xFFFF0000)
static GREEN = new Color(0xFF00FF00)
static BLUE = new Color(0xFF0000FF)
static YELLOW = new Color(0xFFFFFF00)
static CYAN = new Color(0xFF00FFFF)
static MAGENTA = new Color(0xFFFF00FF)
static TRANSPARENT = new Color(0)
_value: number = 0
constructor(v: number) {
this._value = v | 0x0
}
static parse(str: string) {
if (!str.startsWith("#")) {
throw new Error(`Parse color error with ${str}`)
}
const val = parseInt(str.substr(1), 16)
if (str.length === 7) {
return new Color(val | 0xff000000)
} else if (str.length === 9) {
return new Color(val)
} else {
throw new Error(`Parse color error with ${str}`)
}
}
static safeParse(str: string, defVal: Color = Color.TRANSPARENT) {
let color = defVal
try {
color = Color.parse(str)
} catch (e) {
} finally {
return color
}
}
alpha(v: number) {
v = v * 255
return new Color((this._value & 0xffffff) | ((v & 0xff) << 24))
}
toModel() {
return this._value
}
}
export enum GradientOrientation {
/** draw the gradient from the top to the bottom */
TOP_BOTTOM = 0,
/** draw the gradient from the top-right to the bottom-left */
TR_BL,
/** draw the gradient from the right to the left */
RIGHT_LEFT,
/** draw the gradient from the bottom-right to the top-left */
BR_TL,
/** draw the gradient from the bottom to the top */
BOTTOM_TOP,
/** draw the gradient from the bottom-left to the top-right */
BL_TR,
/** draw the gradient from the left to the right */
LEFT_RIGHT,
/** draw the gradient from the top-left to the bottom-right */
TL_BR,
}
export interface GradientColor {
start: Color
end: Color
orientation: GradientOrientation
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Modeling } from "./types";
const SPECIFIED = 1
const START = 1 << 1
const END = 1 << 2
const SHIFT_X = 0
const SHIFT_Y = 4
export const LEFT = (START | SPECIFIED) << SHIFT_X
export const RIGHT = (END | SPECIFIED) << SHIFT_X
export const TOP = (START | SPECIFIED) << SHIFT_Y
export const BOTTOM = (END | SPECIFIED) << SHIFT_Y
export const CENTER_X = SPECIFIED << SHIFT_X
export const CENTER_Y = SPECIFIED << SHIFT_Y
export const CENTER = CENTER_X | CENTER_Y
export class Gravity implements Modeling {
val = 0
left() {
const val = this.val | LEFT
const ret = new Gravity
ret.val = val
return ret
}
right() {
const val = this.val | RIGHT
const ret = new Gravity
ret.val = val
return ret
}
top() {
const val = this.val | TOP
const ret = new Gravity
ret.val = val
return ret
}
bottom() {
const val = this.val | BOTTOM
const ret = new Gravity
ret.val = val
return ret
}
center() {
const val = this.val | CENTER
const ret = new Gravity
ret.val = val
return ret
}
centerX() {
const val = this.val | CENTER_X
const ret = new Gravity
ret.val = val
return ret
}
centerY() {
const val = this.val | CENTER_Y
const ret = new Gravity
ret.val = val
return ret
}
toModel() {
return this.val
}
private static origin = new Gravity
static Center = Gravity.origin.center()
static Left = Gravity.origin.left()
static Right = Gravity.origin.right()
static Top = Gravity.origin.top()
static Bottom = Gravity.origin.bottom()
}
export function gravity() {
return new Gravity
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './color'
export * from './gravity'
export * from './layoutconfig'
export * from './log'
export * from './types'
export * from './uniqueId'

View File

@@ -0,0 +1,122 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Gravity } from "./gravity";
import { Modeling } from "./types";
export enum LayoutSpec {
/**
* Depends on what's been set on width or height.
*/
JUST = 0,
/**
* Depends on it's content.
*/
FIT = 1,
/**
* Extend as much as parent let it take.
*/
MOST = 2,
}
export interface LayoutConfig {
widthSpec?: LayoutSpec
heightSpec?: LayoutSpec
margin?: {
left?: number,
right?: number,
top?: number,
bottom?: number,
}
alignment?: Gravity
//Only affective in VLayout or HLayout
weight?: number
}
export class LayoutConfigImpl implements LayoutConfig, Modeling {
widthSpec?: LayoutSpec
heightSpec?: LayoutSpec
margin?: {
left?: number,
right?: number,
top?: number,
bottom?: number,
}
alignment?: Gravity
//Only affective in VLayout or HLayout
weight?: number
fit() {
this.widthSpec = LayoutSpec.FIT
this.heightSpec = LayoutSpec.FIT
return this
}
most() {
this.widthSpec = LayoutSpec.MOST
this.heightSpec = LayoutSpec.MOST
return this
}
just() {
this.widthSpec = LayoutSpec.JUST
this.heightSpec = LayoutSpec.JUST
return this
}
configWidth(w: LayoutSpec) {
this.widthSpec = w
return this
}
configHeight(h: LayoutSpec) {
this.heightSpec = h
return this
}
configMargin(m: {
left?: number,
right?: number,
top?: number,
bottom?: number,
}) {
this.margin = m
return this
}
configAligmnet(a: Gravity) {
this.alignment = a
return this
}
configWeight(w: number) {
this.weight = w
return this
}
toModel() {
return {
widthSpec: this.widthSpec,
heightSpec: this.heightSpec,
margin: this.margin,
alignment: this.alignment ? this.alignment.toModel() : undefined,
weight: this.weight,
}
}
}
export function layoutConfig() {
return new LayoutConfigImpl
}

65
doric-js/src/util/log.ts Normal file
View File

@@ -0,0 +1,65 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare function nativeLog(type: 'd' | 'w' | 'e', message: string): void
function toString(message: any) {
if (message instanceof Function) {
return message.toString()
} else if (message instanceof Object) {
try {
return JSON.stringify(message)
} catch (e) {
return message.toString()
}
} else if (message === undefined) {
return "undefined"
} else {
return message.toString()
}
}
export function log(...args: any) {
let out = ""
for (let i = 0; i < arguments.length; i++) {
if (i > 0) {
out += ','
}
out += toString(arguments[i])
}
nativeLog('d', out)
}
export function loge(...message: any) {
let out = ""
for (let i = 0; i < arguments.length; i++) {
if (i > 0) {
out += ','
}
out += toString(arguments[i])
}
nativeLog('e', out)
}
export function logw(...message: any) {
let out = ""
for (let i = 0; i < arguments.length; i++) {
if (i > 0) {
out += ','
}
out += toString(arguments[i])
}
nativeLog('w', out)
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Modeling {
toModel(): Model
}
export function obj2Model(obj: Model): Model {
if (obj instanceof Array) {
return obj.map(e => obj2Model(e)) as Model
} else if (obj instanceof Object) {
if (Reflect.has(obj, 'toModel') && Reflect.get(obj, 'toModel') instanceof Function) {
obj = Reflect.apply(Reflect.get(obj, 'toModel'), obj, [])
return obj
} else {
for (let key in obj) {
const val = Reflect.get(obj, key)
Reflect.set(obj, key, obj2Model(val))
}
return obj
}
} else {
return obj
}
}
type _M = string | number | boolean | Modeling | { [index: string]: Model } | undefined
export type Model = _M | Array<_M>
export type Binder<T> = (v: T) => void
export class Mutable<T>{
private val: T
private binders: Set<Binder<T>> = new Set
get = () => {
return this.val
}
set = (v: T) => {
this.val = v
this.binders.forEach(e => {
Reflect.apply(e, undefined, [this.val])
})
}
private constructor(v: T) {
this.val = v
}
bind(binder: Binder<T>) {
this.binders.add(binder)
Reflect.apply(binder, undefined, [this.val])
}
static of<E>(v: E) {
return new Mutable(v)
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let __uniqueId__ = 0
export function uniqueId(prefix: string) {
return `__${prefix}_${__uniqueId__++}__`;
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Stack } from './layouts'
import { Property, IView, Superview, View } from '../ui/view'
import { layoutConfig } from '../util/index.util'
export class FlowLayoutItem extends Stack {
/**
* Set to reuse native view
*/
@Property
identifier?: string
}
export interface IFlowLayout extends IView {
renderItem: (index: number) => FlowLayoutItem
itemCount: number
batchCount?: number
columnCount?: number
columnSpace?: number
rowSpace?: number
}
export class FlowLayout extends Superview implements IFlowLayout {
private cachedViews: Map<string, FlowLayoutItem> = new Map
private ignoreDirtyCallOnce = false
allSubviews() {
if (this.loadMoreView) {
return [...this.cachedViews.values(), this.loadMoreView]
} else {
return this.cachedViews.values()
}
}
@Property
columnCount = 2
@Property
columnSpace?: number
@Property
rowSpace?: number
@Property
itemCount = 0
@Property
renderItem!: (index: number) => FlowLayoutItem
@Property
batchCount = 15
@Property
onLoadMore?: () => void
@Property
loadMore?: boolean
@Property
loadMoreView?: FlowLayoutItem
reset() {
this.cachedViews.clear()
this.itemCount = 0
}
private getItem(itemIdx: number) {
let view = this.renderItem(itemIdx)
view.superview = this
this.cachedViews.set(`${itemIdx}`, view)
return view
}
isDirty() {
if (this.ignoreDirtyCallOnce) {
this.ignoreDirtyCallOnce = false
//Ignore the dirty call once.
return false
}
return super.isDirty()
}
private renderBunchedItems(start: number, length: number) {
this.ignoreDirtyCallOnce = true;
return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => {
const listItem = this.getItem(start + idx)
return listItem.toModel()
})
}
toModel() {
if (this.loadMoreView) {
this.dirtyProps['loadMoreView'] = this.loadMoreView.viewId
}
return super.toModel()
}
}
export function flowlayout(config: IFlowLayout) {
const ret = new FlowLayout
for (let key in config) {
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
}
return ret
}
export function flowItem(item: View) {
return (new FlowLayoutItem).also((it) => {
it.layoutConfig = layoutConfig().fit()
it.addChild(item)
})
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IView, View, Property } from "../ui/view"
import { layoutConfig } from "../util/layoutconfig"
export enum ScaleType {
ScaleToFill = 0,
ScaleAspectFit,
ScaleAspectFill,
}
export interface IImage extends IView {
imageUrl?: string
imageBase64?: string
scaleType?: ScaleType
isBlur?: boolean
loadCallback?: (image: { width: number; height: number } | undefined) => void
}
export class Image extends View implements IImage {
@Property
imageUrl?: string
@Property
imageBase64?: string
@Property
scaleType?: ScaleType
@Property
isBlur?: boolean
@Property
loadCallback?: (image: { width: number; height: number } | undefined) => void
}
export function image(config: IImage) {
const ret = new Image
ret.layoutConfig = layoutConfig().fit()
for (let key in config) {
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
}
return ret
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './layouts'
export * from './text'
export * from './image'
export * from './list'
export * from './slider'
export * from './scroller'
export * from './refreshable'
export * from './flowlayout'
export * from './input'
export * from './nestedSlider'

View File

@@ -0,0 +1,80 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { View, IView, Property } from "../ui/view";
import { Color } from "../util/color";
import { Gravity } from "../util/gravity";
import { BridgeContext } from "../runtime/global";
export interface IInput extends IView {
text?: string
textColor?: Color
textSize?: number
hintText?: string
hintTextColor?: Color
multilines?: boolean
textAlignment?: Gravity
onTextChange?: (text: string) => void
onFocusChange?: (focused: boolean) => void
}
export class Input extends View implements IInput {
@Property
text?: string
@Property
textColor?: Color
@Property
textSize?: number
@Property
hintText?: string
@Property
hintTextColor?: Color
@Property
multiline?: boolean
@Property
textAlignment?: Gravity
@Property
onTextChange?: (text: string) => void
@Property
onFocusChange?: (focused: boolean) => void
getText(context: BridgeContext) {
return this.nativeChannel(context, 'getText')() as Promise<string>
}
setSelection(context: BridgeContext, start: number, end: number = start) {
return this.nativeChannel(context, 'setSelection')({
start,
end,
}) as Promise<string>
}
requestFocus(context: BridgeContext) {
return this.nativeChannel(context, 'requestFocus')()
}
releaseFocus(context: BridgeContext) {
return this.nativeChannel(context, 'releaseFocus')()
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Group, Property, IView, View } from "../ui/view";
import { Gravity } from "../util/gravity";
import { layoutConfig } from "../util/layoutconfig";
export interface IStack extends IView {
}
export class Stack extends Group implements IStack {
}
export class Root extends Stack {
}
class LinearLayout extends Group {
@Property
space?: number
@Property
gravity?: Gravity
}
export interface IVLayout extends IView {
space?: number
gravity?: Gravity
}
export class VLayout extends LinearLayout implements VLayout {
}
export interface IHLayout extends IView {
space?: number
gravity?: Gravity
}
export class HLayout extends LinearLayout implements IHLayout {
}
export function stack(views: View[]) {
const ret = new Stack
ret.layoutConfig = layoutConfig().fit()
for (let v of views) {
ret.addChild(v)
}
return ret
}
export function hlayout(views: View[]) {
const ret = new HLayout
ret.layoutConfig = layoutConfig().fit()
for (let v of views) {
ret.addChild(v)
}
return ret
}
export function vlayout(views: View[]) {
const ret = new VLayout
ret.layoutConfig = layoutConfig().fit()
for (let v of views) {
ret.addChild(v)
}
return ret
}

117
doric-js/src/widget/list.ts Normal file
View File

@@ -0,0 +1,117 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { View, Property, Superview, IView } from "../ui/view";
import { Stack } from "./layouts";
import { layoutConfig, LayoutSpec } from "../util/layoutconfig";
export class ListItem extends Stack {
/**
* Set to reuse native view
*/
@Property
identifier?: string
}
export interface IList extends IView {
renderItem: (index: number) => ListItem
itemCount: number
batchCount?: number
}
export class List extends Superview implements IList {
private cachedViews: Map<string, ListItem> = new Map
private ignoreDirtyCallOnce = false
allSubviews() {
if (this.loadMoreView) {
return [...this.cachedViews.values(), this.loadMoreView]
} else {
return this.cachedViews.values()
}
}
@Property
itemCount = 0
@Property
renderItem!: (index: number) => ListItem
@Property
batchCount = 15
@Property
onLoadMore?: () => void
@Property
loadMore?: boolean
@Property
loadMoreView?: ListItem
reset() {
this.cachedViews.clear()
this.itemCount = 0
}
private getItem(itemIdx: number) {
let view = this.cachedViews.get(`${itemIdx}`)
if (view === undefined) {
view = this.renderItem(itemIdx)
view.superview = this
this.cachedViews.set(`${itemIdx}`, view)
}
return view
}
isDirty() {
if (this.ignoreDirtyCallOnce) {
this.ignoreDirtyCallOnce = false
//Ignore the dirty call once.
return false
}
return super.isDirty()
}
private renderBunchedItems(start: number, length: number) {
this.ignoreDirtyCallOnce = true;
return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => {
const listItem = this.getItem(start + idx)
return listItem.toModel()
})
}
toModel() {
if (this.loadMoreView) {
this.dirtyProps['loadMoreView'] = this.loadMoreView.viewId
}
return super.toModel()
}
}
export function list(config: IList) {
const ret = new List
for (let key in config) {
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
}
return ret
}
export function listItem(item: View) {
return (new ListItem).also((it) => {
it.layoutConfig = layoutConfig().most().configHeight(LayoutSpec.FIT)
it.addChild(item)
})
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Group, View, Property } from '../ui/view'
import { BridgeContext } from '../runtime/global'
export class NestedSlider extends Group {
@Property
onPageSlided?: (index: number) => void
addSlideItem(view: View) {
this.addChild(view)
}
slidePage(context: BridgeContext, page: number, smooth = false) {
return this.nativeChannel(context, "slidePage")({ page, smooth })
}
getSlidedPage(context: BridgeContext) {
return this.nativeChannel(context, "getSlidedPage")() as Promise<number>
}
}

View File

@@ -0,0 +1,74 @@
import { View, Property, Superview, IView } from "../ui/view";
import { List } from "./list";
import { Scroller } from "./scroller";
import { BridgeContext } from "../runtime/global";
import { layoutConfig } from "../util/layoutconfig";
export interface IRefreshable extends IView {
content: View
header?: View
onRefresh?: () => void
}
export class Refreshable extends Superview implements IRefreshable {
content!: List | Scroller
header?: View
@Property
onRefresh?: () => void
allSubviews() {
const ret: View[] = [this.content]
if (this.header) {
ret.push(this.header)
}
return ret
}
setRefreshable(context: BridgeContext, refreshable: boolean) {
return this.nativeChannel(context, 'setRefreshable')(refreshable)
}
setRefreshing(context: BridgeContext, refreshing: boolean) {
return this.nativeChannel(context, 'setRefreshing')(refreshing)
}
isRefreshable(context: BridgeContext) {
return this.nativeChannel(context, 'isRefreshable')() as Promise<boolean>
}
isRefreshing(context: BridgeContext) {
return this.nativeChannel(context, 'isRefreshing')() as Promise<boolean>
}
toModel() {
this.dirtyProps.content = this.content.viewId
this.dirtyProps.header = (this.header || {}).viewId
return super.toModel()
}
}
export function refreshable(config: IRefreshable) {
const ret = new Refreshable
ret.layoutConfig = layoutConfig().fit()
for (let key in config) {
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
}
return ret
}
export interface IPullable {
startAnimation(): void
stopAnimation(): void
setPullingDistance(distance: number): void
}
export function pullable(v: View, config: IPullable) {
Reflect.set(v, 'startAnimation', config.startAnimation)
Reflect.set(v, 'stopAnimation', config.stopAnimation)
Reflect.set(v, 'setPullingDistance', config.setPullingDistance)
return v
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Superview, View, IView } from '../ui/view'
import { layoutConfig } from '../util/layoutconfig'
export function scroller(content: View) {
return (new Scroller).also(v => {
v.layoutConfig = layoutConfig().fit()
v.content = content
})
}
export interface IScroller extends IView {
content: View
}
export class Scroller extends Superview implements IScroller {
content!: View
allSubviews() {
return [this.content]
}
toModel() {
this.dirtyProps.content = this.content.viewId
return super.toModel()
}
}

View File

@@ -0,0 +1,91 @@
import { Superview, View, Property, IView } from "../ui/view";
import { Stack } from "./layouts";
import { layoutConfig } from "../util/layoutconfig";
import { BridgeContext } from "../runtime/global";
export class SlideItem extends Stack {
/**
* Set to reuse native view
*/
@Property
identifier?: string
}
export interface ISlider extends IView {
renderPage: (index: number) => SlideItem
itemCount: number
batchCount?: number
onPageSlided?: (index: number) => void
}
export class Slider extends Superview implements ISlider {
private cachedViews: Map<string, SlideItem> = new Map
private ignoreDirtyCallOnce = false
allSubviews() {
return this.cachedViews.values()
}
@Property
itemCount = 0
@Property
renderPage!: (index: number) => SlideItem
@Property
batchCount = 3
@Property
onPageSlided?: (index: number) => void
private getItem(itemIdx: number) {
let view = this.cachedViews.get(`${itemIdx}`)
if (view === undefined) {
view = this.renderPage(itemIdx)
view.superview = this
this.cachedViews.set(`${itemIdx}`, view)
}
return view
}
isDirty() {
if (this.ignoreDirtyCallOnce) {
this.ignoreDirtyCallOnce = false
//Ignore the dirty call once.
return false
}
return super.isDirty()
}
private renderBunchedItems(start: number, length: number) {
this.ignoreDirtyCallOnce = true;
return new Array(Math.min(length, this.itemCount - start)).fill(0).map((_, idx) => {
const slideItem = this.getItem(start + idx)
return slideItem.toModel()
})
}
slidePage(context: BridgeContext, page: number, smooth = false) {
return this.nativeChannel(context, "slidePage")({ page, smooth })
}
getSlidedPage(context: BridgeContext) {
return this.nativeChannel(context, "getSlidedPage")() as Promise<number>
}
}
export function slideItem(item: View) {
return (new SlideItem).also((it) => {
it.layoutConfig = layoutConfig().fit()
it.addChild(item)
})
}
export function slider(config: ISlider) {
const ret = new Slider
for (let key in config) {
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
}
return ret
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IView, View, Property } from "../ui/view"
import { Color } from "../util/color"
import { Gravity } from "../util/gravity"
import { layoutConfig } from "../util/layoutconfig"
export interface IText extends IView {
text?: string
textColor?: Color
textSize?: number
maxLines?: number
textAlignment?: Gravity
}
export class Text extends View implements IText {
@Property
text?: string
@Property
textColor?: Color
@Property
textSize?: number
@Property
maxLines?: number
@Property
textAlignment?: Gravity
}
export function text(config: IText) {
const ret = new Text
ret.layoutConfig = layoutConfig().fit()
for (let key in config) {
Reflect.set(ret, key, Reflect.get(config, key, config), ret)
}
return ret
}