Refactor Scrolling and Tracks using Panels
In this CL:
* We introduce the interface Panel, which only two methods: renderCanvas
and updateDom.
* Tracks are internally reimplemented as Panels. Track extension API
remains unchanged (except they now get timeScale and
visibleWindowMs through a global.)
* Introduce LightRedrawer, which schedules rafs for high frequency
drawing. All canvas drawing now happens through LightRedrawer,
although m.redraw still remains in most places until we change
overview timeline to work with LightRedrawer.
* Lift out state used by canvas redrawing to global FrontendLocalState
object, since attributes do not propagate through the mithril tree
if m.redraw is not called.
* Scrolling architecture is simplified. CanvasController, Scrollable
Container, and CanvasWrapper are all gone, replaced with the single
ScrollingPanelContainer.
What's not in this CL:
* We don't have a non-scrolling panel container. We can factor out the
common code between scrolling and non-scrolling container and do that
in a seperate CL.
* We don't have any additional panels other than tracks.
Known issues:
* The trace doesn't draw after loading until you pan/zoom for the first
time. We will need to initialize the time scale and visibleWindowMs
properly after a trace load.
Change-Id: I924be19e10e160f710dae920bfa1927e66ec8be9
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
new file mode 100644
index 0000000..6339cd8
--- /dev/null
+++ b/ui/src/frontend/track_panel.ts
@@ -0,0 +1,140 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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 * as m from 'mithril';
+
+import {moveTrack} from '../common/actions';
+import {TrackState} from '../common/state';
+
+import {globals} from './globals';
+import {drawGridLines} from './gridline_helper';
+import {quietDispatch} from './mithril_helpers';
+import {Panel} from './panel';
+import {Track} from './track';
+import {trackRegistry} from './track_registry';
+
+export const TRACK_SHELL_WIDTH = 200;
+
+const TrackShell = {
+ view({attrs}) {
+ return m(
+ '.track-shell',
+ {
+ style: {
+ position: 'absolute',
+ left: '0px',
+ width: `${TRACK_SHELL_WIDTH}px`,
+ 'box-sizing': 'border-box',
+ }
+ },
+ m('h1', attrs.trackState.name),
+ m('.reorder-icons',
+ m(TrackMoveButton, {
+ direction: 'up',
+ trackId: attrs.trackState.id,
+ top: 10,
+ }),
+ m(TrackMoveButton, {
+ direction: 'down',
+ trackId: attrs.trackState.id,
+ top: 40,
+ })));
+ },
+} as m.Component<{trackState: TrackState}>;
+
+const TrackContent = {
+ view({attrs}) {
+ return m('.track-content', {
+ style: {
+ position: 'absolute',
+ left: `${TRACK_SHELL_WIDTH}px`,
+ // TODO: We can use flex-box here and not do this manual
+ // calculation.
+ width: `calc(100% - ${TRACK_SHELL_WIDTH}px)`,
+ height: '100%',
+ },
+ onmousemove: (e: MouseEvent) => {
+ // TODO(hjd): Trigger a repaint here not a full m.redraw.
+ attrs.track.onMouseMove({x: e.layerX, y: e.layerY});
+ m.redraw();
+ },
+ onmouseout: () => {
+ attrs.track.onMouseOut();
+ m.redraw();
+ },
+ }, );
+ }
+} as m.Component<{track: Track}>;
+
+const TrackComponent = {
+ view({attrs}) {
+ return m('.track', [
+ m(TrackShell, {trackState: attrs.trackState}),
+ m(TrackContent, {track: attrs.track})
+ ], );
+ }
+} as m.Component<{trackState: TrackState, track: Track}>;
+
+const TrackMoveButton = {
+ view({attrs}) {
+ return m(
+ 'i.material-icons.track-move-icons',
+ {
+ onclick: quietDispatch(moveTrack(attrs.trackId, attrs.direction)),
+ style: {
+ top: `${attrs.top}px`,
+ }
+ },
+ attrs.direction === 'up' ? 'arrow_upward_alt' : 'arrow_downward_alt');
+ }
+} as m.Component<{
+ direction: 'up' | 'down',
+ trackId: string,
+ top: number,
+},
+ {}>;
+
+export class TrackPanel implements Panel {
+ private track: Track;
+ constructor(public trackState: TrackState) {
+ // TODO: Since ES6 modules are asynchronous and it is conceivable that we
+ // want to load a track implementation on demand, we should not rely here on
+ // the fact that the track is already registered. We should show some
+ // default content until a track implementation is found.
+ const trackCreator = trackRegistry.get(this.trackState.kind);
+ this.track = trackCreator.create(this.trackState);
+ }
+
+ updateDom(dom: Element): void {
+ // TODO: Let tracks render DOM in the content area.
+ m.render(
+ dom,
+ m(TrackComponent, {trackState: this.trackState, track: this.track}));
+ }
+
+ renderCanvas(ctx: CanvasRenderingContext2D) {
+ ctx.translate(TRACK_SHELL_WIDTH, 0);
+ const {visibleWindowMs} = globals.frontendLocalState;
+ drawGridLines(
+ ctx,
+ globals.frontendLocalState.timeScale,
+ [visibleWindowMs.start, visibleWindowMs.end],
+ // TODO: Height should be a property of panel.
+ this.trackState.height);
+
+ const trackData = globals.trackDataStore.get(this.trackState.id);
+ if (trackData !== undefined) this.track.consumeData(trackData);
+ this.track.renderCanvas(ctx);
+ }
+}
\ No newline at end of file