Skip to content

Commit

Permalink
Merge pull request #1341 from cogentcore/screens
Browse files Browse the repository at this point in the history
significantly improved support for multi-monitor setups on desktop
  • Loading branch information
kkoreilly authored Dec 9, 2024
2 parents d170952 + c345955 commit affbc24
Show file tree
Hide file tree
Showing 43 changed files with 1,051 additions and 515 deletions.
116 changes: 77 additions & 39 deletions core/mainstage.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func newMainStage(typ StageTypes, sc *Scene) *Stage {
// to close. It should typically be called once by every app at
// the end of their main function. It can not be called more than
// once for one app. For secondary windows, see [Body.RunWindow].
// If you need to configure the [Stage] further, use [Body.NewWindow]
// and then [Stage.RunMain] on the resulting [Stage].
func (bd *Body) RunMainWindow() {
if ExternalParent != nil {
bd.handleExternalParent()
Expand All @@ -46,6 +48,16 @@ func (bd *Body) RunMainWindow() {
Wait()
}

// RunMain runs the stage, starts the app's main loop,
// and waits for all windows to close. It can be called instead
// of [Body.RunMainWindow] if extra configuration steps are necessary
// on the [Stage]. It can not be called more than once for one app.
// For secondary stages, see [Stage.Run].
func (st *Stage) RunMain() {
st.Run()
Wait()
}

// ExternalParent is a parent widget external to this program.
// If it is set, calls to [Body.RunWindow] before [Wait] and
// calls to [Body.RunMainWindow] will add the [Body] to this
Expand Down Expand Up @@ -88,8 +100,9 @@ func (bd *Body) handleExternalParent() {

// NewWindow returns a new [WindowStage] that is placed in
// a new system window on multi-window platforms.
// You must call [Stage.Run] to run the window; see [Body.RunWindow]
// for a version that automatically runs it.
// You must call [Stage.Run] or [Stage.RunMain] to run the window;
// see [Body.RunWindow] and [Body.RunMainWindow] for versions that
// automatically do so.
func (bd *Body) NewWindow() *Stage {
ms := newMainStage(WindowStage, bd.Scene)
ms.SetNewWindow(true)
Expand Down Expand Up @@ -131,27 +144,29 @@ func (st *Stage) addSceneParts() {
sc.SceneGeom.Pos = np
sc.NeedsRender()
})
rsz := NewHandle(parts)
rsz.Styler(func(s *styles.Style) {
s.Direction = styles.Column
s.FillMargin = false
})
rsz.FinalStyler(func(s *styles.Style) {
s.Cursor = cursors.ResizeNWSE
s.Min.Set(units.Em(1))
})
rsz.SetName("resize")
rsz.OnChange(func(e events.Event) {
e.SetHandled()
pd := e.PrevDelta()
np := sc.SceneGeom.Size.Add(pd)
minsz := 100
np.X = max(np.X, minsz)
np.Y = max(np.Y, minsz)
ng := sc.SceneGeom
ng.Size = np
sc.resize(ng)
})
if st.Resizable {
rsz := NewHandle(parts)
rsz.Styler(func(s *styles.Style) {
s.Direction = styles.Column
s.FillMargin = false
})
rsz.FinalStyler(func(s *styles.Style) {
s.Cursor = cursors.ResizeNWSE
s.Min.Set(units.Em(1))
})
rsz.SetName("resize")
rsz.OnChange(func(e events.Event) {
e.SetHandled()
pd := e.PrevDelta()
np := sc.SceneGeom.Size.Add(pd)
minsz := 100
np.X = max(np.X, minsz)
np.Y = max(np.Y, minsz)
ng := sc.SceneGeom
ng.Size = np
sc.resize(ng)
})
}
}

// firstWindowStages creates a temporary [stages] for the first window
Expand All @@ -163,6 +178,13 @@ func (st *Stage) firstWindowStages() *stages {
return ms
}

func (st *Stage) currentScreen() *system.Screen {
if currentRenderWindow != nil {
return currentRenderWindow.SystemWindow.Screen()
}
return system.TheApp.Screen(0)
}

// configMainStage does main-stage configuration steps
func (st *Stage) configMainStage() {
sc := st.Scene
Expand Down Expand Up @@ -236,17 +258,21 @@ func (st *Stage) runWindow() *Stage {
} else {
// on other platforms, we want extra space and a minimum window size
sz = sz.Add(image.Pt(20, 20))
if st.NewWindow && st.UseMinSize {
// we require windows to be at least 60% and no more than 80% of the
// screen size by default
scsz := system.TheApp.Screen(0).PixSize // TODO(kai): is there a better screen to get here?
sz = image.Pt(max(sz.X, scsz.X*6/10), max(sz.Y, scsz.Y*6/10))
sz = image.Pt(min(sz.X, scsz.X*8/10), min(sz.Y, scsz.Y*8/10))
screen := st.currentScreen()
if screen != nil {
st.SetScreen(screen.ScreenNumber)
if st.NewWindow && st.UseMinSize {
// we require windows to be at least 60% and no more than 80% of the
// screen size by default
scsz := screen.PixSize
sz = image.Pt(max(sz.X, scsz.X*6/10), max(sz.Y, scsz.Y*6/10))
sz = image.Pt(min(sz.X, scsz.X*8/10), min(sz.Y, scsz.Y*8/10))
}
}
}
}
st.Mains = nil // reset
if DebugSettings.WinRenderTrace {
if DebugSettings.WindowRenderTrace {
fmt.Println("MainStage.RunWindow: Window Size:", sz)
}

Expand Down Expand Up @@ -325,8 +351,12 @@ func (st *Stage) runDialog() *Stage {
sz.X = max(sz.X, minx)
}
sc.Events.startFocusFirst = true // popup dialogs always need focus
screen := st.currentScreen()
if screen != nil {
st.SetScreen(screen.ScreenNumber)
}
}
if DebugSettings.WinRenderTrace {
if DebugSettings.WindowRenderTrace {
slog.Info("MainStage.RunDialog", "size", sz)
}

Expand Down Expand Up @@ -355,21 +385,29 @@ func (st *Stage) newRenderWindow() *renderWindow {
Title: title,
Icon: appIconImages(),
Size: st.Scene.SceneGeom.Size,
Pos: st.Pos,
StdPixels: false,
Screen: st.Screen,
}
wgp := theWindowGeometrySaver.pref(title, nil)
if TheApp.Platform() != system.Offscreen && wgp != nil {
opts.Flags.SetFlag(!st.Resizable, system.FixedSize)
opts.Flags.SetFlag(st.Maximized, system.Maximized)
opts.Flags.SetFlag(st.Fullscreen, system.Fullscreen)
screenName := ""
if st.Screen > 0 {
screenName = TheApp.Screen(st.Screen).Name
}
wgp, screen := theWindowGeometrySaver.get(title, screenName)
if wgp != nil {
theWindowGeometrySaver.settingStart()
opts.Size = wgp.size()
opts.Pos = wgp.pos()
opts.Screen = screen.ScreenNumber
opts.Size = wgp.Size
opts.Pos = wgp.Pos
opts.StdPixels = false
if w := AllRenderWindows.FindName(name); w != nil { // offset from existing
opts.Pos.X += 20
opts.Pos.Y += 20
}
if wgp.Fullscreen {
opts.SetFullscreen()
}
opts.Flags.SetFlag(wgp.Max, system.Maximized)
}
win := newRenderWindow(name, title, opts)
theWindowGeometrySaver.settingEnd()
Expand Down Expand Up @@ -409,7 +447,7 @@ func (sm *stages) mainHandleEvent(e events.Event) {
for i := n - 1; i >= 0; i-- {
st := sm.stack.ValueByIndex(i)
st.mainHandleEvent(e)
if e.IsHandled() || st.Modal || st.Type == WindowStage || st.FullWindow {
if e.IsHandled() || st.Modal || st.FullWindow {
break
}
if st.Type == DialogStage { // modeless dialog, by definition
Expand Down
17 changes: 15 additions & 2 deletions core/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,24 @@ func (sc *Scene) standardContextMenu(m *Scene) { //types:add
InspectorWindow(sc)
})

// no window menu on single-window platforms
if TheApp.Platform().IsMobile() {
// No window menu on mobile platforms
if TheApp.Platform().IsMobile() && TheApp.Platform() != system.Web {
return
}
NewButton(m).SetText("Window").SetMenu(func(m *Scene) {
if sc.IsFullscreen() {
NewButton(m).SetText("Exit fullscreen").SetIcon(icons.Fullscreen).OnClick(func(e events.Event) {
sc.SetFullscreen(false)
})
} else {
NewButton(m).SetText("Fullscreen").SetIcon(icons.Fullscreen).OnClick(func(e events.Event) {
sc.SetFullscreen(true)
})
}
// Only do fullscreen on web
if TheApp.Platform() == system.Web {
return
}
NewButton(m).SetText("Focus next").SetIcon(icons.CenterFocusStrong).
SetKey(keymap.WinFocusNext).OnClick(func(e events.Event) {
AllRenderWindows.focusNext()
Expand Down
Loading

0 comments on commit affbc24

Please sign in to comment.