pxt-badge/schedule.ts

1324 строки
40 KiB
TypeScript

namespace ui {
export function width(val: number): Style {
return new Style(StyleName.width, val);
}
export function height(val: number): Style {
return new Style(StyleName.height, val);
}
export function paddingLeft(val: number): Style {
return new Style(StyleName.paddingLeft, val);
}
export function paddingTop(val: number): Style {
return new Style(StyleName.paddingTop, val);
}
export function paddingRight(val: number): Style {
return new Style(StyleName.paddingRight, val);
}
export function paddingBottom(val: number): Style {
return new Style(StyleName.paddingBottom, val);
}
export function borderColor(val: number): Style {
return new Style(StyleName.borderColor, val);
}
export function borderLeft(val: number): Style {
return new Style(StyleName.borderLeft, val);
}
export function borderTop(val: number): Style {
return new Style(StyleName.borderTop, val);
}
export function borderRight(val: number): Style {
return new Style(StyleName.borderRight, val);
}
export function borderBottom(val: number): Style {
return new Style(StyleName.borderBottom, val);
}
export function color(val: number): Style {
return new Style(StyleName.color, val);
}
export function padding(val: number): Style {
return new Style(StyleName.padding, val);
}
export function border(val: number): Style {
return new Style(StyleName.border, val);
}
export function alignLeft(): Style {
return new Style(StyleName.contentAlign, ContentAlign.Left);
}
export function alignCenter(): Style {
return new Style(StyleName.contentAlign, ContentAlign.Center);
}
export function alignRight(): Style {
return new Style(StyleName.contentAlign, ContentAlign.Right);
}
export function smallFont(): Style {
return new Style(StyleName.font, Font.Small);
}
export function animate(doAnimate: boolean) {
return new Style(StyleName.animate, doAnimate ? 1 : 0)
}
export function className(name: string): Style {
const res = new Style(StyleName.className);
res.stringValue = name;
return res;
}
}
namespace ui {
export const WRAP = -1;
export const FILL = -2;
export enum StyleName {
width,
height,
paddingLeft,
paddingTop,
paddingRight,
paddingBottom,
padding,
borderColor,
borderLeft,
borderTop,
borderRight,
borderBottom,
border,
color,
contentAlign,
font,
className,
animate
}
export enum ContentAlign {
Left,
Center,
Right
}
export enum Font {
Normal,
Small
}
export class Style {
readonly name: StyleName;
value: number;
stringValue: string;
constructor(name: StyleName, value?: number) {
this.name = name;
this.value = value;
}
}
export class StyleRule {
readonly className: string;
protected styles: Style[];
constructor(className: string, styles: Style[]) {
this.className = className;
this.styles = styles || [];
}
getStyles() {
return this.styles;
}
add(s: Style) {
if (s) {
for (const style of this.styles) {
if (style.name === s.name) {
style.value = s.value;
return;
}
}
this.styles.push(s);
}
}
}
export class StyleSheet {
protected rules: StyleRule[];
constructor() {
this.rules = [];
}
createClass(name: string, styles: Style[]) {
this.addRule(new StyleRule(name, styles));
}
addRule(r: StyleRule) {
for (const rule of this.rules) {
if (rule.className === r.className) {
for (const s of r.getStyles()) {
rule.add(s);
}
return;
}
}
this.rules.push(r);
}
getStylesForClass(className: string): Style[] {
for (const rule of this.rules) {
if (rule.className === className) {
return rule.getStyles();
}
}
return [];
}
}
}
namespace ui {
export class BoundingBox {
left: number;
top: number;
width: number;
height: number;
}
export class BoxValues {
protected data: Buffer;
constructor() {
this.data = control.createBuffer(4);
}
get left() {
return this.data[0];
}
set left(val: number) {
this.data[0] = val & 0xff;
}
get top() {
return this.data[1];
}
set top(val: number) {
this.data[1] = val & 0xff;
}
get right() {
return this.data[2];
}
set right(val: number) {
this.data[2] = val & 0xff;
}
get bottom() {
return this.data[3];
}
set bottom(val: number) {
this.data[3] = val & 0xff;
}
}
export class ContentBox {
padding: BoxValues;
border: BoxValues;
align: ContentAlign;
borderColor: number;
constructor() {
this.padding = new BoxValues();
this.border = new BoxValues();
this.borderColor = 0;
this.align = ContentAlign.Center;
}
getElementBounds(left: number, top: number, outerWidth: number, outerHeight: number) {
const r = new BoundingBox();
r.left = left + this.border.left;
r.top = top + this.border.top;
r.width = outerWidth - this.border.left - this.border.right;
r.height = outerHeight - this.border.top - this.border.bottom;
return r;
}
getContentBounds(element: BoundingBox, contentWidth: number, contentHeight: number) {
const r = new BoundingBox();
r.top = element.top + this.padding.top;
r.width = contentWidth;
r.height = contentHeight;
switch (this.align) {
case ContentAlign.Left:
r.left = element.left + this.padding.left;
break;
case ContentAlign.Center:
r.left = element.left + (element.width >> 1) - (contentWidth >> 1);
break;
case ContentAlign.Right:
r.left = (element.left + element.width - this.padding.right - contentWidth);
break;
}
return r;
}
}
export class Element {
parent: Element;
children: Element[];
contentBox: ContentBox;
verticalFlow: boolean;
width: number;
height: number;
_cachedWidth: number;
_cachedHeight: number;
_renderedBounds: BoundingBox;
protected sheet: StyleSheet;
protected classes: string[];
constructor() {
this.verticalFlow = true;
this.width = WRAP;
this.height = WRAP;
this.contentBox = new ContentBox();
}
appendChild(child: Element) {
if (!this.children) this.children = [];
if (child.parent) {
child.parent.removeChild(child);
}
child.parent = this;
this.children.push(child);
}
removeChild(child: Element) {
if (this.children) this.children.removeElement(child);
}
defineStyleClass(className: string, styles?: Style[]) {
if (!this.sheet) this.sheet = new StyleSheet();
const rule = new StyleRule(className, styles);
this.sheet.addRule(rule);
return rule;
}
draw() {
if (!this._renderedBounds) {
this.render();
}
this.drawSelf(this._renderedBounds);
this.drawBorder();
if (this.children) {
for (const child of this.children) {
child.draw();
}
}
}
render(bounds?: BoundingBox) {
if (this._renderedBounds) return;
this.applyClassStyles();
if (bounds) {
this._renderedBounds = this.contentBox.getElementBounds(bounds.left, bounds.top, bounds.width, bounds.height)
}
else {
this._renderedBounds = this.contentBox.getElementBounds(0, 0, calculateWidth(this), calculateHeight(this));
}
this.onDidReceiveBounds(this._renderedBounds);
if (this.children) {
if (this.verticalFlow) this.renderVerticalFlow();
else this.renderHorizontalFlow();
}
}
markDirty() {
this._cachedWidth = undefined;
this._cachedHeight = undefined;
this._renderedBounds = undefined;
if (this.children) this.children.forEach(c => c.markDirty());
}
applyStyles(styles: Style[]) {
for (const style of styles) {
if (style) this.applyStyle(style);
}
}
protected applyClassStyles() {
if (this._renderedBounds) return;
if (this.classes) this.classes.forEach(c => this.applyStylesForClass(c));
if (this.children) this.children.forEach(c => c.applyClassStyles());
}
protected onDidReceiveBounds(bounds: BoundingBox) {
// subclass
}
protected applyStylesForClass(className: string) {
let classStyles: Style[];
let current: Element = this;
while (current) {
if (current.sheet) {
classStyles = current.sheet.getStylesForClass(className);
if (classStyles) {
break;
}
}
current = current.parent;
}
if (classStyles && classStyles.length) {
for (const style of classStyles) {
if (style) this.applyStyle(style);
}
}
}
protected renderVerticalFlow() {
let y = this._renderedBounds.top + this.contentBox.padding.top + this.contentBox.border.top;
let current: BoundingBox;
for (const child of this.children) {
current = this.contentBox.getContentBounds(this._renderedBounds, calculateWidth(child), calculateHeight(child));
current.top = y;
child.render(current);
y += current.height;
}
}
protected renderHorizontalFlow() {
let x = this._renderedBounds.left + this.contentBox.padding.left + this.contentBox.border.left;
let current: BoundingBox;
for (const child of this.children) {
current = this.contentBox.getContentBounds(this._renderedBounds, calculateWidth(child), calculateHeight(child));
current.left = x;
child.render(current);
x += current.width;
}
}
protected drawSelf(bounds: BoundingBox) {
// subclass
}
protected applyStyle(style: Style) {
switch (style.name) {
case StyleName.width: this.width = style.value; return;
case StyleName.height: this.height = style.value; return;
case StyleName.borderColor: this.contentBox.borderColor = style.value; return;
case StyleName.borderLeft: this.contentBox.border.left = style.value; return;
case StyleName.borderRight: this.contentBox.border.right = style.value; return;
case StyleName.borderTop: this.contentBox.border.top = style.value; return;
case StyleName.borderBottom: this.contentBox.border.bottom = style.value; return;
case StyleName.contentAlign: this.contentBox.align = style.value; break;
case StyleName.paddingLeft: this.contentBox.padding.left = style.value; return;
case StyleName.paddingRight: this.contentBox.padding.right = style.value; return;
case StyleName.paddingTop: this.contentBox.padding.top = style.value; return;
case StyleName.paddingBottom: this.contentBox.padding.bottom = style.value; return;
case StyleName.padding:
this.contentBox.padding.left = style.value;
this.contentBox.padding.right = style.value;
this.contentBox.padding.top = style.value;
this.contentBox.padding.bottom = style.value;
break;
case StyleName.border:
this.contentBox.border.left = style.value;
this.contentBox.border.right = style.value;
this.contentBox.border.top = style.value;
this.contentBox.border.bottom = style.value;
break;
case StyleName.className:
if (!this.classes) this.classes = [];
this.classes.push(style.stringValue);
break;
}
}
protected drawBorder() {
if (this.contentBox.borderColor === 0) return;
if (this.contentBox.border.left) {
screen.fillRect(
this._renderedBounds.left - this.contentBox.border.left,
this._renderedBounds.top - this.contentBox.border.top,
this.contentBox.border.left,
this._renderedBounds.height + this.contentBox.border.top + this.contentBox.border.bottom,
this.contentBox.borderColor
);
}
if (this.contentBox.border.right) {
screen.fillRect(
this._renderedBounds.left + this._renderedBounds.width,
this._renderedBounds.top - this.contentBox.border.top,
this.contentBox.border.right,
this._renderedBounds.height + this.contentBox.border.top + this.contentBox.border.bottom,
this.contentBox.borderColor
);
}
if (this.contentBox.border.top) {
screen.fillRect(
this._renderedBounds.left - this.contentBox.border.left,
this._renderedBounds.top - this.contentBox.border.top,
this._renderedBounds.width + this.contentBox.border.left + this.contentBox.border.right,
this.contentBox.border.top,
this.contentBox.borderColor
);
}
if (this.contentBox.border.bottom) {
screen.fillRect(
this._renderedBounds.left - this.contentBox.border.left,
this._renderedBounds.top + this._renderedBounds.height,
this._renderedBounds.width + this.contentBox.border.left + this.contentBox.border.right,
this.contentBox.border.bottom,
this.contentBox.borderColor
);
}
}
}
function calculateWidth(element: Element): number {
if (!element) return screen.width;
else if (element._cachedWidth != undefined) return element._cachedWidth;
else if (element.width === WRAP) return getWRAPWidth(element);
else if (element.width === FILL) return element._cachedWidth = contentWidth(element.parent);
else return element._cachedWidth = element.width;
}
function calculateHeight(element: Element): number {
if (!element) return screen.height;
else if (element._cachedHeight != undefined) return element._cachedHeight;
else if (element.height === WRAP) return getWRAPHeight(element);
else if (element.height === FILL) return element._cachedHeight = contentHeight(element.parent);
else return element._cachedHeight = element.height;
}
function getWRAPWidth(element: Element) {
if (element._cachedWidth != undefined) return element._cachedWidth;
let childWidth = 0;
if (element.width !== WRAP && element.width !== FILL) {
return element._cachedWidth = element.width;
}
else if (element.children) {
if (element.verticalFlow) {
let maxWidth = 0;
for (const child of element.children) {
maxWidth = Math.max(getWRAPWidth(child), maxWidth)
}
childWidth = maxWidth;
}
else {
let totalWidth = 0;
for (const child of element.children) {
totalWidth += getWRAPWidth(child);
}
childWidth = totalWidth;
}
}
childWidth += element.contentBox.padding.left +
element.contentBox.padding.right +
element.contentBox.border.left +
element.contentBox.border.right;
if (element.width === WRAP) {
element._cachedWidth = childWidth;
}
return childWidth;
}
function getWRAPHeight(element: Element) {
if (element._cachedHeight != undefined) return element._cachedHeight;
let childHeight = 0;
if (element.height !== WRAP && element.height !== FILL) {
return element._cachedHeight = element.height;
}
else if (element.children) {
if (element.verticalFlow) {
let totalHeight = 0;
for (const child of element.children) {
totalHeight += getWRAPHeight(child);
}
childHeight = totalHeight;
}
else {
let maxHeight = 0;
for (const child of element.children) {
maxHeight = Math.max(getWRAPHeight(child), maxHeight)
}
childHeight = maxHeight;
}
}
childHeight += element.contentBox.padding.top +
element.contentBox.padding.bottom +
element.contentBox.border.top +
element.contentBox.border.bottom;
if (element.height === WRAP) {
element._cachedHeight = childHeight;
}
return childHeight;
}
function contentWidth(element: Element) {
if (!element) return screen.width;
else {
return calculateWidth(element) -
element.contentBox.padding.left -
element.contentBox.padding.right -
element.contentBox.border.left -
element.contentBox.border.right;
}
}
function contentHeight(element: Element) {
if (!element) return screen.height;
else {
return calculateHeight(element) -
element.contentBox.padding.top -
element.contentBox.padding.bottom -
element.contentBox.border.top -
element.contentBox.border.bottom;
}
}
}
namespace ui {
export function box(child?: Element, styles?: Style[]) {
const box = new BoxElement();
if (child) {
box.appendChild(child);
}
if (styles) {
box.applyStyles(styles);
}
return box;
}
export function text(content: string, styles?: Style[]) {
const text = new TextElement(content);
if (styles) {
text.applyStyles(styles);
}
return text;
}
export function longText(content: string, styles?: Style[]) {
const text = new LongTextElement(content);
if (styles) {
text.applyStyles(styles);
}
return text;
}
export function verticalFlow(children: Element[], styles?: Style[]) {
const container = new Element();
if (styles) {
container.applyStyles(styles);
}
for (const child of children) {
container.appendChild(child)
}
return container;
}
export function horizontalFlow(children: Element[], styles?: Style[]) {
const res = verticalFlow(children, styles);
res.verticalFlow = false;
return res;
}
export function scrollingLabel(label: string, maxWidth: number, styles?: Style[]) {
const el = new ScrollingTextElement(label, maxWidth);
if (styles) {
el.applyStyles(styles);
}
return el;
}
}
namespace ui {
export class ShapeElement extends Element {
color: number;
constructor() {
super();
this.color = 0;
}
protected drawSelf(bounds: BoundingBox) {
if (this.color) this.drawShape(bounds);
}
protected drawShape(bounds: BoundingBox) {
screen.drawRect(bounds.left, bounds.top, bounds.width, bounds.height, this.color);
}
applyStyle(style: Style) {
if (style.name === StyleName.color) {
this.color = style.value
}
else {
super.applyStyle(style);
}
}
}
export class BoxElement extends ShapeElement {
protected drawShape(bounds: BoundingBox) {
screen.fillRect(bounds.left, bounds.top, bounds.width, bounds.height, this.color);
}
}
export class TextElement extends ShapeElement {
text: string;
font: Font;
constructor(text?: string) {
super();
this.setText(text);
this.color = 15;
this.font = Font.Normal;
}
setText(text: string) {
this.text = text;
this.updateBounds();
}
applyStyle(style: Style) {
if (style.name === StyleName.font) {
this.font = style.value;
this.updateBounds();
}
else {
super.applyStyle(style);
}
}
protected updateBounds() {
const f = this.renderFont();
this.height = f.charHeight;
if (this.text) {
this.width = this.text.length * f.charWidth;
}
else {
this.width = 0;
}
}
protected drawShape(bounds: BoundingBox) {
screen.print(this.text, bounds.left, bounds.top, this.color, this.renderFont());
}
protected renderFont() {
if (this.font === Font.Small) {
return image.font5
}
return image.font8;
}
}
export class ScrollingTextElement extends TextElement {
protected partialCanvas: Image;
protected offset: number;
protected pauseTime: number;
protected stage: number;
protected timer: number;
protected maxCharacters: number;
protected maxOffset: number;
protected scrolling: boolean;
constructor(text: string, maxWidth: number) {
super(text);
this.pauseTime = 1000;
this.timer = this.pauseTime;
this.stage = 0;
this.offset = 0;
this.width = maxWidth;
this.updateBounds();
}
applyStyle(style: Style) {
if (style.name === StyleName.animate) {
this.scrolling = !!style.value
}
else {
super.applyStyle(style);
}
}
protected updateBounds() {
const f = this.renderFont();
this.height = f.charHeight;
const fullLength = this.text.length * f.charWidth;
this.maxCharacters = Math.idiv(this.width, f.charWidth);
this.maxOffset = fullLength - this.maxCharacters * f.charWidth;
this.partialCanvas = image.create(f.charWidth, f.charHeight);
}
protected drawShape(bounds: BoundingBox) {
const font = this.renderFont();
const startIndex = Math.idiv(this.offset, font.charWidth);
const letterOffset = startIndex * font.charWidth - this.offset;
if (!this.scrolling) {
this.offset = 0;
}
else if (this.stage === 1) {
this.offset++;
if (this.offset >= this.maxOffset) {
this.stage++;
this.offset = Math.max(this.maxOffset, 0);
}
}
else {
if (this.stage === 0) {
this.offset = 0;
}
else if (this.stage === 2) {
this.offset = Math.max(this.maxOffset, 0);
}
this.timer -= game.currentScene().eventContext.deltaTimeMillis;
if (this.timer < 0) {
this.stage = (this.stage + 1) % 3;
this.timer = this.pauseTime;
}
}
if (letterOffset) {
this.partialCanvas.fill(0);
this.partialCanvas.print(this.text.charAt(startIndex), letterOffset, 0, this.color, font)
screen.drawTransparentImage(this.partialCanvas, bounds.left, bounds.top);
}
else {
screen.print(this.text.charAt(startIndex), bounds.left, bounds.top, this.color, font);
}
for (let i = 1; i < this.maxCharacters; i++) {
screen.print(
this.text.charAt(startIndex + i),
bounds.left + i * font.charWidth + letterOffset,
bounds.top,
this.color,
font
);
}
}
}
export class ImageElement extends Element {
protected src: Image;
constructor(src: Image) {
super();
this.src = src;
}
protected drawSelf(bounds: BoundingBox) {
screen.drawTransparentImage(this.src, bounds.left, bounds.top);
}
}
export class DynamicElement extends Element {
protected drawFunction: (bounds: BoundingBox) => void;
constructor(drawFunction: (bounds: BoundingBox) => void) {
super();
this.drawFunction = drawFunction;
}
protected drawSelf(bounds: BoundingBox) {
this.drawFunction(bounds);
}
}
export class LongTextElement extends Element {
protected dialog: game.Dialog;
protected text: string;
constructor(text: string) {
super();
this.text = text;
}
protected onDidReceiveBounds(bounds: BoundingBox) {
this.dialog = new game.Dialog(bounds.width, bounds.height, null, null, image.create(1, 1))
this.dialog.setText(this.text);
this.dialog.update();
}
protected drawSelf(bounds: BoundingBox) {
screen.drawTransparentImage(this.dialog.image, bounds.left, bounds.top);
}
}
}
namespace ui {
let _state: AppState;
function initState() {
_state = {
view: AppView.DaySelection,
selectedDay: 0,
selectedEvent: 0,
selectedButton: 0,
feedbackStep: 0,
};
}
export function getState() {
if (!_state) initState();
return _state;
}
export function clearState() {
_state = undefined;
}
}
namespace ui {
export enum AppView {
DaySelection,
EventList,
EventOptions,
EventFeedback,
EventInfo
}
export interface AppState {
view: AppView;
selectedDay: number;
selectedEvent: number;
selectedButton: number;
feedbackStep: number;
}
export function main() {
const state = getState();
scene.setBackgroundColor(12)
let el: Element;
el = mainView();
controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
switch (state.view) {
case AppView.DaySelection:
state.view = AppView.EventList;
break;
case AppView.EventList:
state.view = AppView.EventOptions;
break;
case AppView.EventOptions:
state.view = state.selectedButton ? AppView.EventFeedback : AppView.EventInfo;
break;
case AppView.EventInfo:
break;
case AppView.EventFeedback:
break;
}
el = mainView();
});
controller.B.onEvent(ControllerButtonEvent.Pressed, function () {
switch (state.view) {
case AppView.DaySelection:
storyboard.replace("home");
break;
case AppView.EventList:
state.view = AppView.DaySelection;
state.selectedEvent = 0;
break;
case AppView.EventOptions:
state.view = AppView.EventList;
state.selectedButton = 0;
break;
case AppView.EventInfo:
state.view = AppView.EventOptions;
break;
case AppView.EventFeedback:
state.view = AppView.EventOptions;
state.selectedButton = 0;
break;
}
el = mainView();
});
controller.down.onEvent(ControllerButtonEvent.Pressed, function () {
const program = badge.program;
switch (state.view) {
case AppView.DaySelection:
state.selectedDay = (state.selectedDay + 1) % program.days.length;
break;
case AppView.EventList:
state.selectedEvent = (state.selectedEvent + 1) % program.sessions.length;
break;
case AppView.EventOptions:
break;
case AppView.EventInfo:
break;
case AppView.EventFeedback:
state.selectedButton = (state.selectedButton + 1) % 5
break;
}
el = mainView();
});
controller.up.onEvent(ControllerButtonEvent.Pressed, function () {
const program = badge.program;
switch (state.view) {
case AppView.DaySelection:
state.selectedDay = (state.selectedDay + program.days.length - 1) % program.days.length;
break;
case AppView.EventList:
state.selectedEvent = (state.selectedEvent + program.sessions.length - 1) % program.sessions.length;
break;
case AppView.EventOptions:
break;
case AppView.EventInfo:
break;
case AppView.EventFeedback:
state.selectedButton = (state.selectedButton - 1) % 5
break;
}
el = mainView();
});
controller.right.onEvent(ControllerButtonEvent.Pressed, function () {
switch (state.view) {
case AppView.EventOptions:
state.selectedButton = (state.selectedButton + 1) % 2
break;
case AppView.DaySelection:
case AppView.EventList:
case AppView.EventFeedback:
case AppView.EventInfo:
break;
}
el = mainView();
});
controller.left.onEvent(ControllerButtonEvent.Pressed, function () {
switch (state.view) {
case AppView.EventOptions:
state.selectedButton = (state.selectedButton + 1) % 2
break;
case AppView.DaySelection:
case AppView.EventList:
case AppView.EventFeedback:
case AppView.EventInfo:
break;
}
el = mainView();
});
game.onShade(function () {
el.draw();
});
}
function mainView() {
const state = getState();
let el: Element;
switch (state.view) {
case AppView.DaySelection:
el = dayView();
break;
case AppView.EventList:
el = listView();
break;
case AppView.EventOptions:
el = eventOptionsView();
break;
case AppView.EventInfo:
el = eventInfoView();
break;
case AppView.EventFeedback:
el = eventFeedbackView();
break;
}
createStyles(el)
return el;
}
function createStyles(el: Element) {
el.defineStyleClass("text-box", [
padding(2),
alignLeft(),
width(FILL)
]);
el.defineStyleClass("card-title", [
color(1),
]);
el.defineStyleClass("selected", [
animate(true),
]);
el.defineStyleClass("card-detail", [
color(15),
smallFont()
]);
el.defineStyleClass("container", [
width(screen.width),
padding(5),
color(12)
]);
el.defineStyleClass("container-header", [
color(1)
]);
el.defineStyleClass("button", [
color(11),
width(75),
padding(2)
]);
el.defineStyleClass("button-selected", [
color(3)
]);
el.defineStyleClass("button-text", [
color(1)
]);
}
function dayView() {
const state = getState();
const program = badge.program;
return titleView("Schedule", listFlow(program.days.map((day, i) =>
cardView(day.title, i === state.selectedDay, day.weekday, `May ${day.monthday}th`)
)));
}
function titleView(title: string, content: Element) {
return verticalFlow([
box(
text(title),
[className("container"), className("container-header")]
),
box(
content,
[className("container")]
)
]);
}
function listView() {
const state = getState();
const program = badge.program;
return titleView(program.days[state.selectedDay].title, listFlow(program.sessions.map((s, i) =>
sessionCard(s, screen.width - 10, i === state.selectedEvent)
)));
}
function listFlow(items: Element[]) {
const content: Element[] = [];
for (const item of items) {
content.push(item);
// Empty 1-pixel spacer
content.push(box(null, [padding(1)]))
}
return verticalFlow(content, [width(FILL)]);
}
function sessionCard(s: badge.Session, cardWidth: number, selected = false) {
const select = selected && className("selected");
return cardView(s.name, selected, formatTime(s.startTime), s.presenter);
}
function cardView(title: string, selected: boolean, detail: string, detailRight: string) {
const select = selected && className("selected");
return verticalFlow([
box(
scrollingLabel(title, screen.width - 10, [className("card-title"), select]),
[color(selected ? 3 : 11), className("text-box")]
),
box(horizontalFlow([
scrollingLabel(detail, 65, [className("card-detail"), select]),
box(
scrollingLabel(detailRight, 50, [className("card-detail"), select]),
[width(65), alignRight()])
]), [color(1), className("text-box")]),
], [width(FILL)]);
}
function eventFeedbackView() {
const state = getState();
const program = badge.program;
const sessions = program.sessions;
const days = program.days;
const event = sessions[state.selectedEvent];
const day = days[state.selectedDay];
return titleView("Rate this session",
verticalFlow([
box(
text("1 - :C", [className("button-text")]),
[className("button"), state.selectedButton === 1 && className("button-selected")]
),
box(null, [padding(1)]),
box(
text("2 - :(", [className("button-text")]),
[className("button"), state.selectedButton === 2 && className("button-selected")]
),
box(null, [padding(1)]),
box(
text("3 - :|", [className("button-text")]),
[className("button"), state.selectedButton === 3 && className("button-selected")]
),
box(null, [padding(1)]),
box(
text("4 - :)", [className("button-text")]),
[className("button"), state.selectedButton === 4 && className("button-selected")]
),
box(null, [padding(1)]),
box(
text("5 - :D", [className("button-text")]),
[className("button"), state.selectedButton === 0 && className("button-selected")]
)
])
);
}
function eventOptionsView() {
const state = getState();
const program = badge.program;
const sessions = program.sessions;
const days = program.days;
const event = sessions[state.selectedEvent];
const day = days[state.selectedDay];
return titleView(event.name,
verticalFlow([
box(
scrollingLabel(`${formatTime(event.startTime)} at ${event.location}`, screen.width - 20, [animate(true), color(1), smallFont()]),
[padding(2), width(154)]),
box(
longText(event.info, [width(158), height(60)]),
[paddingBottom(5), paddingTop(5)]
),
horizontalFlow([
box(
text("Info", [className("button-text")]),
[className("button"), state.selectedButton === 0 && className("button-selected")]
),
box(null, [padding(1)]),
box(
text("Feedback", [className("button-text")]),
[className("button"), state.selectedButton === 1 && className("button-selected")]
)
])
])
);
}
function eventInfoView() {
const state = getState();
const program = badge.program;
const sessions = program.sessions;
const days = program.days;
const event = sessions[state.selectedEvent];
const day = days[state.selectedDay];
return titleView("Event Info", listFlow([
eventItem("Title", event.name),
eventItem("Presenter", event.presenter),
eventItem("Location", event.location),
eventItem("Time", `${formatTime(event.startTime)} - ${formatTime(event.endTime)}`),
eventItem("Day", `${day.weekday}, May ${day.monthday}th`),
eventItem("Type", "Breakout"),
]))
}
function eventItem(title: string, value: string) {
return box(
horizontalFlow([
box(
text(title + ":", [color(1)]),
[color(3), padding(2), width(WRAP)]),
box(
scrollingLabel(value, 90, [animate(true)]),
[padding(2)]
)
]),
[color(1), alignLeft(), width(FILL)])
}
function formatTime(time: number) {
const hour = Math.idiv(time, 100) % 12;
const minute = time % 100;
return `${hour}:${minute < 10 ? "0" + minute : minute}${time >= 1200 ? "pm" : "am"}`
}
storyboard.registerScene("schedule", main)
}