Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add a system for theming, use it to implement dark mode #323

Merged
merged 27 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
eb0c089
Define Theme
jlfwong Nov 8, 2020
2eaeb5b
Replace usages of Colors.WHITE
jlfwong Nov 8, 2020
1e6e502
Replace usages of Colors.LIGHT_GRAY
jlfwong Nov 8, 2020
602d450
Remove usage of GRAY
jlfwong Nov 8, 2020
6b8e4c4
Remove BRIGHT_BLUE and accentColor
jlfwong Nov 8, 2020
5ccf17c
Replace usages of Color.BLACK
jlfwong Nov 8, 2020
4180d1e
Replace usages of DARK_BLUE
jlfwong Nov 8, 2020
a0ec9f6
Replace usages of PALE_DARK_BLUE
jlfwong Nov 8, 2020
313e834
Replace usages of GREEN
jlfwong Nov 8, 2020
7676da1
Replace usages of TRANSPARENT_GREEN
jlfwong Nov 8, 2020
105a117
Replace usages of YELLOW and ORANGE
jlfwong Nov 8, 2020
15b735b
Cleanup, fix TypeScript errors
jlfwong Nov 8, 2020
b7cd79e
Extract functions mapping from t -> color into Theme
jlfwong Nov 8, 2020
3005779
Fill in missing usages of Theme
jlfwong Nov 8, 2020
575d5b2
Fix redundant calls to clearColor
jlfwong Nov 8, 2020
b2672e8
fixup! Fill in missing usages of Theme
jlfwong Nov 8, 2020
b16b65f
Refine theme, style scrollbars
jlfwong Nov 8, 2020
e7cd635
progres...
jlfwong Nov 8, 2020
5ce4ea8
Thread theme through component hierachy
jlfwong Nov 8, 2020
b758a8a
Add theme provider using matchMedia
jlfwong Nov 8, 2020
215019f
Fix errors caught by linters & typechecker
jlfwong Nov 8, 2020
4c5b3c8
Use memoizeByShallowEquality for friendly syntax, add event listeners
jlfwong Nov 8, 2020
2a19c36
Ensure we re-draw after the WebGL canvas resizes
jlfwong Nov 12, 2020
f3e2119
Increase contrast for search results
jlfwong Nov 12, 2020
c46af71
Add button to toggle color scheme
jlfwong Nov 12, 2020
b7af76b
Persist color scheme prefence in localStorage
jlfwong Nov 12, 2020
7639bc4
Run prettier
jlfwong Nov 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions src/gl/canvas-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {TextureRenderer} from './texture-renderer'
import {Rect, Vec2} from '../lib/math'
import {ViewportRectangleRenderer} from './overlay-rectangle-renderer'
import {FlamechartColorPassRenderer} from './flamechart-color-pass-renderer'
import {Color} from '../lib/color'
import {Theme} from '../views/themes/theme'

type FrameCallback = () => void

Expand All @@ -13,13 +15,19 @@ export class CanvasContext {
public readonly textureRenderer: TextureRenderer
public readonly viewportRectangleRenderer: ViewportRectangleRenderer
public readonly flamechartColorPassRenderer: FlamechartColorPassRenderer
public readonly theme: Theme

constructor(canvas: HTMLCanvasElement) {
constructor(canvas: HTMLCanvasElement, theme: Theme) {
this.gl = new WebGL.Context(canvas)
this.rectangleBatchRenderer = new RectangleBatchRenderer(this.gl)
this.textureRenderer = new TextureRenderer(this.gl)
this.viewportRectangleRenderer = new ViewportRectangleRenderer(this.gl)
this.flamechartColorPassRenderer = new FlamechartColorPassRenderer(this.gl)
this.viewportRectangleRenderer = new ViewportRectangleRenderer(this.gl, theme)
this.flamechartColorPassRenderer = new FlamechartColorPassRenderer(this.gl, theme)
this.theme = theme

// Whenever the canvas is resized, draw immediately. This prevents
// flickering during resizing.
this.gl.addAfterResizeEventHandler(this.onBeforeFrame)

const webGLInfo = this.gl.getWebGLInfo()
if (webGLInfo) {
Expand Down Expand Up @@ -48,7 +56,8 @@ export class CanvasContext {
private onBeforeFrame = () => {
this.animationFrameRequest = null
this.gl.setViewport(0, 0, this.gl.renderTargetWidthInPixels, this.gl.renderTargetHeightInPixels)
this.gl.clear(new Graphics.Color(1, 1, 1, 1))
const color = Color.fromCSSHex(this.theme.bgPrimaryColor)
this.gl.clear(new Graphics.Color(color.r, color.g, color.b, color.a))

for (const handler of this.beforeFrameHandlers) {
handler()
Expand Down
15 changes: 5 additions & 10 deletions src/gl/flamechart-color-pass-renderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Vec2, Rect, AffineTransform} from '../lib/math'
import {Theme} from '../views/themes/theme'
import {Graphics} from './graphics'
import {setUniformAffineTransform} from './utils'

Expand All @@ -20,7 +21,7 @@ const vert = `
}
`

const frag = `
const frag = (colorForBucket: string) => `
precision mediump float;

uniform vec2 uvSpacePixelSize;
Expand Down Expand Up @@ -49,13 +50,7 @@ const frag = `
return 2.0 * abs(fract(x) - 0.5) - 1.0;
}

vec3 colorForBucket(float t) {
float x = triangle(30.0 * t);
float H = 360.0 * (0.9 * t);
float C = 0.25 + 0.2 * x;
float L = 0.80 - 0.15 * x;
return hcl2rgb(H, C, L);
}
${colorForBucket}

void main() {
vec4 here = texture2D(colorTexture, vUv);
Expand Down Expand Up @@ -107,7 +102,7 @@ export class FlamechartColorPassRenderer {
private material: Graphics.Material
private buffer: Graphics.VertexBuffer

constructor(private gl: Graphics.Context) {
constructor(private gl: Graphics.Context, theme: Theme) {
const vertices = [
{pos: [-1, 1], uv: [0, 1]},
{pos: [1, 1], uv: [1, 1]},
Expand All @@ -124,7 +119,7 @@ export class FlamechartColorPassRenderer {

this.buffer = gl.createVertexBuffer(vertexFormat.stride * vertices.length)
this.buffer.uploadFloats(floats)
this.material = gl.createMaterial(vertexFormat, vert, frag)
this.material = gl.createMaterial(vertexFormat, vert, frag(theme.colorForBucketGLSL))
}

render(props: FlamechartColorPassRenderProps) {
Expand Down
21 changes: 20 additions & 1 deletion src/gl/graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ export namespace Graphics {
public alphaF: number,
) {}

equals(other: Color): boolean {
return (
this.redF === other.redF &&
this.greenF === other.greenF &&
this.blueF === other.blueF &&
this.alphaF === other.alphaF
)
}

static TRANSPARENT = new Color(0, 0, 0, 0)
}

Expand Down Expand Up @@ -160,6 +169,14 @@ export namespace Graphics {
setUnpremultipliedBlendState() {
this.setBlendState(BlendOperation.SOURCE_ALPHA, BlendOperation.INVERSE_SOURCE_ALPHA)
}

protected resizeEventHandlers = new Set<() => void>()
addAfterResizeEventHandler(callback: () => void): void {
this.resizeEventHandlers.add(callback)
}
removeAfterResizeEventHandler(callback: () => void): void {
this.resizeEventHandlers.delete(callback)
}
}

export interface Material {
Expand Down Expand Up @@ -476,13 +493,15 @@ export namespace WebGL {
this.setViewport(0, 0, widthInPixels, heightInPixels)
this._width = widthInPixels
this._height = heightInPixels

this.resizeEventHandlers.forEach(cb => cb())
}

clear(color: Graphics.Color) {
this._updateRenderTargetAndViewport()
this._updateBlendState()

if (color != this._currentClearColor) {
if (!color.equals(this._currentClearColor)) {
this._gl.clearColor(color.redF, color.greenF, color.blueF, color.alphaF)
this._currentClearColor = color
}
Expand Down
90 changes: 48 additions & 42 deletions src/gl/overlay-rectangle-renderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {Color} from '../lib/color'
import {AffineTransform, Rect} from '../lib/math'
import {Theme} from '../views/themes/theme'
import {Graphics} from './graphics'
import {setUniformAffineTransform, setUniformVec2} from './utils'

Expand All @@ -18,53 +20,57 @@ const vert = `
}
`

const frag = `
precision mediump float;

uniform mat3 configSpaceToPhysicalViewSpace;
uniform vec2 physicalSize;
uniform vec2 physicalOrigin;
uniform vec2 configSpaceViewportOrigin;
uniform vec2 configSpaceViewportSize;
uniform float framebufferHeight;

void main() {
vec2 origin = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportOrigin, 1.0)).xy;
vec2 size = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportSize, 0.0)).xy;

vec2 halfSize = physicalSize / 2.0;

float borderWidth = 2.0;

origin = floor(origin * halfSize) / halfSize + borderWidth * vec2(1.0, 1.0);
size = floor(size * halfSize) / halfSize - 2.0 * borderWidth * vec2(1.0, 1.0);

vec2 coord = gl_FragCoord.xy;
coord.x = coord.x - physicalOrigin.x;
coord.y = framebufferHeight - coord.y - physicalOrigin.y;
vec2 clamped = clamp(coord, origin, origin + size);
vec2 gap = clamped - coord;
float maxdist = max(abs(gap.x), abs(gap.y));

// TOOD(jlfwong): Could probably optimize this to use mix somehow.
if (maxdist == 0.0) {
// Inside viewport rectangle
gl_FragColor = vec4(0, 0, 0, 0);
} else if (maxdist < borderWidth) {
// Inside viewport rectangle at border
gl_FragColor = vec4(0.7, 0.7, 0.7, 0.8);
} else {
// Outside viewport rectangle
gl_FragColor = vec4(0.7, 0.7, 0.7, 0.5);
const frag = (theme: Theme) => {
const {r, g, b} = Color.fromCSSHex(theme.fgSecondaryColor)
const rgb = `${r.toFixed(1)}, ${g.toFixed(1)}, ${b.toFixed(1)}`
return `
precision mediump float;

uniform mat3 configSpaceToPhysicalViewSpace;
uniform vec2 physicalSize;
uniform vec2 physicalOrigin;
uniform vec2 configSpaceViewportOrigin;
uniform vec2 configSpaceViewportSize;
uniform float framebufferHeight;

void main() {
vec2 origin = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportOrigin, 1.0)).xy;
vec2 size = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportSize, 0.0)).xy;

vec2 halfSize = physicalSize / 2.0;

float borderWidth = 2.0;

origin = floor(origin * halfSize) / halfSize + borderWidth * vec2(1.0, 1.0);
size = floor(size * halfSize) / halfSize - 2.0 * borderWidth * vec2(1.0, 1.0);

vec2 coord = gl_FragCoord.xy;
coord.x = coord.x - physicalOrigin.x;
coord.y = framebufferHeight - coord.y - physicalOrigin.y;
vec2 clamped = clamp(coord, origin, origin + size);
vec2 gap = clamped - coord;
float maxdist = max(abs(gap.x), abs(gap.y));

// TOOD(jlfwong): Could probably optimize this to use mix somehow.
if (maxdist == 0.0) {
// Inside viewport rectangle
gl_FragColor = vec4(0, 0, 0, 0);
} else if (maxdist < borderWidth) {
// Inside viewport rectangle at border
gl_FragColor = vec4(${rgb}, 0.8);
} else {
// Outside viewport rectangle
gl_FragColor = vec4(${rgb}, 0.5);
}
}
}
`
`
}

export class ViewportRectangleRenderer {
private material: Graphics.Material
private buffer: Graphics.VertexBuffer

constructor(private gl: Graphics.Context) {
constructor(private gl: Graphics.Context, theme: Theme) {
const vertices = [
[-1, 1],
[1, 1],
Expand All @@ -78,7 +84,7 @@ export class ViewportRectangleRenderer {
}
this.buffer = gl.createVertexBuffer(vertexFormat.stride * vertices.length)
this.buffer.upload(new Uint8Array(new Float32Array(floats).buffer))
this.material = gl.createMaterial(vertexFormat, vert, frag)
this.material = gl.createMaterial(vertexFormat, vert, frag(theme))
}

render(props: ViewportRectangleRendererProps) {
Expand Down
17 changes: 17 additions & 0 deletions src/lib/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ export class Color {
return new Color(clamp(R1 + m, 0, 1), clamp(G1 + m, 0, 1), clamp(B1 + m, 0, 1), 1.0)
}

static fromCSSHex(hex: string) {
if (hex.length !== 7 || hex[0] !== '#') {
throw new Error(`Invalid color input ${hex}`)
}
const r = parseInt(hex.substr(1, 2), 16) / 255
const g = parseInt(hex.substr(3, 2), 16) / 255
const b = parseInt(hex.substr(5, 2), 16) / 255
if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) {
throw new Error(`Invalid color input ${hex}`)
}
return new Color(r, g, b)
}

withAlpha(a: number): Color {
return new Color(this.r, this.g, this.b, a)
}

toCSS(): string {
return `rgba(${(255 * this.r).toFixed()}, ${(255 * this.g).toFixed()}, ${(
255 * this.b
Expand Down
5 changes: 4 additions & 1 deletion src/speedscope.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {h, render} from 'preact'
import {createAppStore} from './store'
import {ApplicationContainer} from './views/application-container'
import {Provider} from './lib/preact-redux'
import {ThemeProvider} from './views/themes/theme'

console.log(`speedscope v${require('../package.json').version}`)

Expand All @@ -20,7 +21,9 @@ const store = lastStore ? createAppStore(lastStore.getState()) : createAppStore(

render(
<Provider store={store}>
<ApplicationContainer />
<ThemeProvider>
<ApplicationContainer />
</ThemeProvider>
</Provider>,
document.body,
document.body.lastElementChild || undefined,
Expand Down
3 changes: 2 additions & 1 deletion src/store/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {actionCreator} from '../lib/typed-redux'
import {CallTreeNode, Frame, ProfileGroup} from '../lib/profile'
import {SortMethod} from '../views/profile-table-view'
import {ViewMode} from '.'
import {ColorScheme, ViewMode} from '.'
import {FlamechartID} from './flamechart-view-state'
import {Rect, Vec2} from '../lib/math'
import {HashParams} from '../lib/hash-params'
Expand All @@ -19,6 +19,7 @@ export namespace actions {
export const setLoading = actionCreator<boolean>('setLoading')
export const setError = actionCreator<boolean>('setError')
export const setHashParams = actionCreator<HashParams>('setHashParams')
export const setColorScheme = actionCreator<ColorScheme>('setColorScheme')

export namespace sandwichView {
export const setTableSortMethod = actionCreator<SortMethod>('sandwichView.setTableSortMethod')
Expand Down
29 changes: 16 additions & 13 deletions src/store/getters.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {Frame, Profile} from '../lib/profile'
import {triangle, memoizeByReference, memoizeByShallowEquality} from '../lib/utils'
import {memoizeByReference, memoizeByShallowEquality} from '../lib/utils'
import {RowAtlas} from '../gl/row-atlas'
import {CanvasContext} from '../gl/canvas-context'
import {Color} from '../lib/color'
import {FlamechartRowAtlasKey} from '../gl/flamechart-renderer'
import {Theme} from '../views/themes/theme'

export const createGetColorBucketForFrame = memoizeByReference(
(frameToColorBucket: Map<number | string, number>) => {
Expand All @@ -13,24 +13,27 @@ export const createGetColorBucketForFrame = memoizeByReference(
},
)

export const createGetCSSColorForFrame = memoizeByReference(
(frameToColorBucket: Map<number | string, number>) => {
export const createGetCSSColorForFrame = memoizeByShallowEquality(
({
theme,
frameToColorBucket,
}: {
theme: Theme
frameToColorBucket: Map<number | string, number>
}) => {
const getColorBucketForFrame = createGetColorBucketForFrame(frameToColorBucket)
return (frame: Frame): string => {
const t = getColorBucketForFrame(frame) / 255

const x = triangle(30.0 * t)
const H = 360.0 * (0.9 * t)
const C = 0.25 + 0.2 * x
const L = 0.8 - 0.15 * x
return Color.fromLumaChromaHue(L, C, H).toCSS()
return theme.colorForBucket(t).toCSS()
}
},
)

export const getCanvasContext = memoizeByReference((canvas: HTMLCanvasElement) => {
return new CanvasContext(canvas)
})
export const getCanvasContext = memoizeByShallowEquality(
({theme, canvas}: {theme: Theme; canvas: HTMLCanvasElement}) => {
return new CanvasContext(canvas, theme)
},
)

export const getRowAtlas = memoizeByReference((canvasContext: CanvasContext) => {
return new RowAtlas<FlamechartRowAtlasKey>(
Expand Down
Loading