diff --git a/app/internal/wm/GioView.java b/app/internal/wm/GioView.java index 7ed9f052..356773c5 100644 --- a/app/internal/wm/GioView.java +++ b/app/internal/wm/GioView.java @@ -29,6 +29,8 @@ import android.view.WindowInsets; import android.view.Surface; import android.view.SurfaceView; import android.view.SurfaceHolder; +import android.view.Window; +import android.view.WindowInsetsController; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; @@ -132,6 +134,70 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal setPointerIcon(pointerIcon); } + private enum Bar { + NAVIGATION, + STATUS, + } + + private void setBarColor(Bar t, int color, int luminance) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return; + } + + Window window = ((Activity) this.getContext()).getWindow(); + + int insetsMask; + int viewMask; + + switch (t) { + case STATUS: + insetsMask = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; + viewMask = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + window.setStatusBarColor(color); + break; + case NAVIGATION: + insetsMask = WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; + viewMask = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + window.setNavigationBarColor(color); + break; + default: + throw new RuntimeException("invalid bar type"); + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return; + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + int flags = this.getSystemUiVisibility(); + if (luminance > 128) { + flags |= viewMask; + } else { + flags &= ~viewMask; + } + this.setSystemUiVisibility(flags); + return; + } + + WindowInsetsController insetsController = window.getInsetsController(); + if (insetsController == null) { + return; + } + if (luminance > 128) { + insetsController.setSystemBarsAppearance(insetsMask, insetsMask); + } else { + insetsController.setSystemBarsAppearance(0, insetsMask); + } + } + + private void setStatusColor(int color, int luminance) { + this.setBarColor(Bar.STATUS, color, luminance); + } + + private void setNavigationColor(int color, int luminance) { + this.setBarColor(Bar.NAVIGATION, color, luminance); + } + private void dispatchMotionEvent(MotionEvent event) { for (int j = 0; j < event.getHistorySize(); j++) { long time = event.getHistoricalEventTime(j); diff --git a/app/internal/wm/os_android.go b/app/internal/wm/os_android.go index 287ef5bf..62f4d61b 100644 --- a/app/internal/wm/os_android.go +++ b/app/internal/wm/os_android.go @@ -42,7 +42,9 @@ import "C" import ( "errors" "fmt" + "gioui.org/internal/f32color" "image" + "image/color" "reflect" "runtime" "runtime/debug" @@ -82,18 +84,22 @@ type window struct { // windowState tracks the View or Activity specific state lost when Android // re-creates our Activity. type windowState struct { - cursor *pointer.CursorName + cursor *pointer.CursorName + navigationColor *color.NRGBA + statusColor *color.NRGBA } // gioView hold cached JNI methods for GioView. var gioView struct { - once sync.Once - getDensity C.jmethodID - getFontScale C.jmethodID - showTextInput C.jmethodID - hideTextInput C.jmethodID - postFrameCallback C.jmethodID - setCursor C.jmethodID + once sync.Once + getDensity C.jmethodID + getFontScale C.jmethodID + showTextInput C.jmethodID + hideTextInput C.jmethodID + postFrameCallback C.jmethodID + setCursor C.jmethodID + setNavigationColor C.jmethodID + setStatusColor C.jmethodID } // ViewEvent is sent whenever the Window's underlying Android view @@ -220,6 +226,8 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V") m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V") m.setCursor = getMethodID(env, class, "setCursor", "(I)V") + m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V") + m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V") }) view = C.gio_jni_NewGlobalRef(env, view) wopts := <-mainWindow.out @@ -235,6 +243,7 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j handle := C.jlong(view) views[handle] = w w.loadConfig(env, class) + w.Option(wopts.opts) applyStateDiff(env, view, windowState{}, w.state) w.setStage(system.StagePaused) w.callbacks.Event(ViewEvent{View: uintptr(view)}) @@ -676,7 +685,18 @@ func (w *window) ReadClipboard() { }) } -func (w *window) Option(opts *Options) {} +func (w *window) Option(opts *Options) { + if o := opts.NavigationColor; o != nil { + w.setState(func(state *windowState) { + state.navigationColor = o + }) + } + if o := opts.StatusColor; o != nil { + w.setState(func(state *windowState) { + state.statusColor = o + }) + } +} func (w *window) SetCursor(name pointer.CursorName) { w.setState(func(state *windowState) { @@ -703,6 +723,12 @@ func applyStateDiff(env *C.JNIEnv, view C.jobject, old, state windowState) { if state.cursor != nil && old.cursor != state.cursor { setCursor(env, view, *state.cursor) } + if state.navigationColor != nil && old.navigationColor != state.navigationColor { + setNavigationColor(env, view, *state.navigationColor) + } + if state.statusColor != nil && old.statusColor != state.statusColor { + setStatusColor(env, view, *state.statusColor) + } } func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) { @@ -728,6 +754,20 @@ func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) { callVoidMethod(env, view, gioView.setCursor, jvalue(curID)) } +func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) { + callVoidMethod(env, view, gioView.setStatusColor, + jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)), + jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)), + ) +} + +func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) { + callVoidMethod(env, view, gioView.setNavigationColor, + jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)), + jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)), + ) +} + // Close the window. Not implemented for Android. func (w *window) Close() {} diff --git a/app/internal/wm/os_js.go b/app/internal/wm/os_js.go index d239ae9a..85bd2936 100644 --- a/app/internal/wm/os_js.go +++ b/app/internal/wm/os_js.go @@ -3,7 +3,10 @@ package wm import ( + "fmt" + "gioui.org/internal/f32color" "image" + "image/color" "strings" "sync" "syscall/js" @@ -22,6 +25,7 @@ import ( type window struct { window js.Value document js.Value + head js.Value clipboard js.Value cnv js.Value tarea js.Value @@ -61,6 +65,7 @@ func NewWindow(win Callbacks, opts *Options) error { document: doc, tarea: tarea, window: js.Global().Get("window"), + head: doc.Get("head"), clipboard: js.Global().Get("navigator").Get("clipboard"), } w.requestAnimationFrame = w.window.Get("requestAnimationFrame") @@ -486,6 +491,9 @@ func (w *window) Option(opts *Options) { if o := opts.WindowMode; o != nil { w.windowMode(*o) } + if o := opts.NavigationColor; o != nil { + w.navigationColor(*o) + } } func (w *window) SetCursor(name pointer.CursorName) { @@ -567,7 +575,7 @@ func (w *window) config() (int, int, system.Insets, unit.Metric) { func (w *window) windowMode(mode WindowMode) { switch mode { case Windowed: - if fs := w.document.Get("fullscreenElement"); !fs.Truthy() { + if !w.document.Get("fullscreenElement").Truthy() { return // Browser is already Windowed. } if !w.document.Get("exitFullscreen").Truthy() { @@ -583,6 +591,17 @@ func (w *window) windowMode(mode WindowMode) { } } +func (w *window) navigationColor(c color.NRGBA) { + theme := w.head.Call("querySelector", `meta[name="theme-color"]`) + if !theme.Truthy() { + theme = w.document.Call("createElement", "meta") + theme.Set("name", "theme-color") + w.head.Call("appendChild", theme) + } + rgba := f32color.NRGBAToRGBA(c) + theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B})) +} + func Main() { select {} } diff --git a/app/internal/wm/window.go b/app/internal/wm/window.go index 168d4d6d..1cfb79af 100644 --- a/app/internal/wm/window.go +++ b/app/internal/wm/window.go @@ -6,6 +6,7 @@ package wm import ( "errors" + "image/color" "gioui.org/gpu" "gioui.org/io/event" @@ -20,11 +21,13 @@ type Size struct { } type Options struct { - Size *Size - MinSize *Size - MaxSize *Size - Title *string - WindowMode *WindowMode + Size *Size + MinSize *Size + MaxSize *Size + Title *string + WindowMode *WindowMode + StatusColor *color.NRGBA + NavigationColor *color.NRGBA } type WindowMode uint8 diff --git a/app/window.go b/app/window.go index 20ee8b07..83f9c5e3 100644 --- a/app/window.go +++ b/app/window.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "image" + "image/color" "time" "gioui.org/io/event" @@ -525,4 +526,18 @@ func MinSize(w, h unit.Value) Option { } } +// StatusColor sets the color of the Android status bar. +func StatusColor(color color.NRGBA) Option { + return func(opts *wm.Options) { + opts.StatusColor = &color + } +} + +// NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers. +func NavigationColor(color color.NRGBA) Option { + return func(opts *wm.Options) { + opts.NavigationColor = &color + } +} + func (driverEvent) ImplementsEvent() {}