mirror of
https://github.com/clearlinux/clr-installer.git
synced 2026-04-28 11:13:46 +00:00
main menu: add new redesigned main menu
Signed-off-by: Leandro Dorileo <leandro.maciel.dorileo@intel.com>
This commit is contained in:
committed by
Leandro Dorileo
parent
c2654ea92f
commit
68bd76bea6
@@ -25,7 +25,7 @@ import (
|
||||
// Also used by the Makefile for releases.
|
||||
// Default to the version of the program
|
||||
// but may be overridden for demo/documentation mode.
|
||||
var Version = "0.8.0"
|
||||
var Version = "0.9.0"
|
||||
|
||||
// SystemInstall represents the system install "configuration", the target
|
||||
// medias, bundles to install and whatever state a install may require
|
||||
|
||||
@@ -102,14 +102,14 @@ Tab.ViewBack = white
|
||||
Tab.ViewText = blue
|
||||
|
||||
// ---- Main menu items ---------------------
|
||||
MenuButtonText = white bold
|
||||
MenuButtonBack = blue
|
||||
Main.MenuText = white bold
|
||||
Main.MenuBack = blue
|
||||
|
||||
MenuButtonActiveText=blue bold
|
||||
MenuButtonActiveBack=white
|
||||
Main.MenuActiveText=blue bold
|
||||
Main.MenuActiveBack=white
|
||||
|
||||
SubMenuButtonText=white
|
||||
SubMenuButtonBack=blue
|
||||
Main.MenuContentText=white
|
||||
Main.MenuContentBack=blue
|
||||
|
||||
SubMenuButtonActiveText=blue
|
||||
SubMenuButtonActiveBack=white
|
||||
Main.MenuContentActiveText=blue
|
||||
Main.MenuContentActiveBack=white
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright © 2018 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
)
|
||||
|
||||
// AdvancedSubMenuPage is the Page implementation for Advance/Optional configuration options
|
||||
type AdvancedSubMenuPage struct {
|
||||
BasePage
|
||||
btns []*SimpleButton
|
||||
}
|
||||
|
||||
func (page *AdvancedSubMenuPage) addMenuItem(item Page) bool {
|
||||
|
||||
buttonPrefix := page.GetButtonPrefix(item)
|
||||
title := fmt.Sprintf(" %s %s", buttonPrefix, item.GetMenuTitle())
|
||||
btn := CreateSimpleButton(page.content, AutoSize, AutoSize, title, Fixed)
|
||||
btn.SetStyle("Menu")
|
||||
btn.SetAlign(AlignLeft)
|
||||
|
||||
btn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(item.GetID())
|
||||
})
|
||||
|
||||
page.btns = append(page.btns, btn)
|
||||
|
||||
return buttonPrefix != MenuButtonPrefixUncompleted
|
||||
}
|
||||
|
||||
// Activate is called when the page is "shown" and it repaints the main menu based on the
|
||||
// available menu pages and their done/undone status
|
||||
func (page *AdvancedSubMenuPage) Activate() {
|
||||
for _, curr := range page.btns {
|
||||
curr.Destroy()
|
||||
}
|
||||
page.btns = []*SimpleButton{}
|
||||
|
||||
previous := false
|
||||
activeSet := false
|
||||
for _, curr := range page.tui.pages {
|
||||
// Skip Menu Pages that are not required
|
||||
if curr.IsRequired() || curr.GetMenuTitle() == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if page.tui.prevPage != nil {
|
||||
// Is this menu option match the previous page?
|
||||
previous = page.tui.prevPage.GetID() == curr.GetID()
|
||||
}
|
||||
|
||||
// Does the menu item added have the data set completed?
|
||||
completed := page.addMenuItem(curr)
|
||||
|
||||
// If we haven't found the first active choice, set it
|
||||
if !activeSet && !completed {
|
||||
// Make last button added Active
|
||||
page.activated = page.btns[len(page.btns)-1]
|
||||
activeSet = true
|
||||
}
|
||||
|
||||
// Special case if the previous page and the data set is not completed
|
||||
// we want THIS to be the active choice for easy return
|
||||
if previous && !completed {
|
||||
// Make last button added Active
|
||||
page.activated = page.btns[len(page.btns)-1]
|
||||
activeSet = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
advancedDesc = `Advanced/Optional configuration items which influence the Installer. Use <Tab> or arrow keys (up and down) to navigate between the elements.`
|
||||
)
|
||||
|
||||
// The Advanced page gives the user the option so select how to set the storage device,
|
||||
// if to manually configure it or a guided standard partition schema
|
||||
func newAdvancedPage(tui *Tui) (Page, error) {
|
||||
page := &AdvancedSubMenuPage{
|
||||
BasePage: BasePage{
|
||||
// Tag this Page as required to be complete for the Install to proceed
|
||||
required: true,
|
||||
},
|
||||
}
|
||||
|
||||
page.setupMenu(tui, TuiPageAdvancedMenu, "Advanced/Optional Menu", BackButton, TuiPageMenu)
|
||||
|
||||
lbl := clui.CreateLabel(page.content, 70, 3, advancedDesc, Fixed)
|
||||
lbl.SetMultiline(true)
|
||||
|
||||
return page, nil
|
||||
}
|
||||
@@ -33,11 +33,19 @@ To enable post-installation, use:
|
||||
`
|
||||
)
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (aup *AutoUpdatePage) GetConfiguredValue() string {
|
||||
if aup.getModel().AutoUpdate {
|
||||
return "Enabled"
|
||||
}
|
||||
return "Disabled"
|
||||
}
|
||||
|
||||
func newAutoUpdatePage(tui *Tui) (Page, error) {
|
||||
page := &AutoUpdatePage{}
|
||||
|
||||
page.setupMenu(tui, TuiPageAutoUpdate, "Automatic OS Updates",
|
||||
BackButton|DoneButton, TuiPageAdvancedMenu)
|
||||
BackButton|DoneButton, TuiPageMenu)
|
||||
|
||||
lbl := clui.CreateLabel(page.content, 2, 16, autoUpdateHelp, Fixed)
|
||||
lbl.SetMultiline(true)
|
||||
|
||||
@@ -6,6 +6,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
"github.com/clearlinux/clr-installer/swupd"
|
||||
@@ -26,6 +27,11 @@ var (
|
||||
bundles = []*BundleCheck{}
|
||||
)
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (bp *BundlePage) GetConfiguredValue() string {
|
||||
return strings.Join(bp.getModel().Bundles, ", ")
|
||||
}
|
||||
|
||||
// Activate marks the checkbox selections based on the data model
|
||||
func (bp *BundlePage) Activate() {
|
||||
model := bp.getModel()
|
||||
@@ -52,7 +58,7 @@ func newBundlePage(tui *Tui) (Page, error) {
|
||||
}
|
||||
|
||||
page := &BundlePage{}
|
||||
page.setupMenu(tui, TuiPageBundle, "Bundle Selection", NoButtons, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageBundle, "Bundle Selection", NoButtons, TuiPageMenu)
|
||||
|
||||
clui.CreateLabel(page.content, 2, 2, "Select additional bundles to be added to the system", Fixed)
|
||||
|
||||
@@ -74,7 +80,7 @@ func newBundlePage(tui *Tui) (Page, error) {
|
||||
|
||||
cancelBtn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
cancelBtn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
confirmBtn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Confirm", Fixed)
|
||||
@@ -90,7 +96,7 @@ func newBundlePage(tui *Tui) (Page, error) {
|
||||
}
|
||||
|
||||
page.SetDone(anySelected)
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
return page, nil
|
||||
|
||||
106
tui/common.go
106
tui/common.go
@@ -5,9 +5,6 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/clearlinux/clr-installer/model"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
@@ -17,21 +14,21 @@ import (
|
||||
// BasePage is the common implementation for the TUI frontend
|
||||
// other pages will inherit this base page behaviours
|
||||
type BasePage struct {
|
||||
tui *Tui // the Tui frontend reference
|
||||
window *clui.Window // the page window
|
||||
mFrame *clui.Frame // main frame
|
||||
content *clui.Frame // the main content frame
|
||||
cFrame *clui.Frame // control frame
|
||||
cancelBtn *SimpleButton // cancel button
|
||||
backBtn *SimpleButton // back button
|
||||
doneBtn *SimpleButton // done button
|
||||
activated clui.Control // activated control
|
||||
menuTitle string // the title used to show on main menu
|
||||
done bool // marks if an item is completed
|
||||
id int // the page id
|
||||
data interface{} // arbitrary page context data
|
||||
action int // indicates if the user has performed a navigation action
|
||||
required bool // marks if an item is required for the install
|
||||
tui *Tui // the Tui frontend reference
|
||||
window *clui.Window // the page window
|
||||
content *clui.Frame // the main content frame
|
||||
cFrame *clui.Frame // control frame
|
||||
cancelBtn *SimpleButton // cancel button
|
||||
backBtn *SimpleButton // back button
|
||||
doneBtn *SimpleButton // done button
|
||||
activated clui.Control // activated control
|
||||
menuTitle string // the title used to show on main menu
|
||||
done bool // marks if an item is completed
|
||||
id int // the page id
|
||||
data interface{} // arbitrary page context data
|
||||
action int // indicates if the user has performed a navigation action
|
||||
required bool // marks if an item is required for the install
|
||||
menuButton *MenuButton
|
||||
}
|
||||
|
||||
// Page defines the methods a Page must implement
|
||||
@@ -48,7 +45,9 @@ type Page interface {
|
||||
Activate()
|
||||
DeActivate()
|
||||
GetConfigDefinition() int
|
||||
GetButtonPrefix(item Page) string
|
||||
GetConfiguredValue() string
|
||||
SetMenuButton(mb *MenuButton)
|
||||
GetMenuButton() *MenuButton
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -57,14 +56,8 @@ const (
|
||||
// WindowHeight is our desired terminal width
|
||||
WindowHeight = 24
|
||||
|
||||
// MenuButtonPrefixUncompleted is the standard, uncompleted prefix for a menu button
|
||||
MenuButtonPrefixUncompleted = "[ ]"
|
||||
// MenuButtonPrefixCompletedByConfig is the completed by config prefix for a menu button
|
||||
MenuButtonPrefixCompletedByConfig = "[*]"
|
||||
// MenuButtonPrefixCompletedByUser is the completed by user prefix for a menu button
|
||||
MenuButtonPrefixCompletedByUser = "[+]"
|
||||
// MenuButtonPrefixSubMenu is the prefix for a sub-menu button
|
||||
MenuButtonPrefixSubMenu = "-->"
|
||||
// ContentHeight is content frame height
|
||||
ContentHeight = 15
|
||||
|
||||
// AutoSize is shortcut for clui.AutoSize flag
|
||||
AutoSize = clui.AutoSize
|
||||
@@ -150,9 +143,6 @@ const (
|
||||
// TuiPageKernel is the id for the kernel selection page
|
||||
TuiPageKernel
|
||||
|
||||
// TuiPageAdvancedMenu is the id for Advanced/Optional configuration menu
|
||||
TuiPageAdvancedMenu
|
||||
|
||||
// TuiPageSwupdMirror is the id for the swupd mirror page
|
||||
TuiPageSwupdMirror
|
||||
|
||||
@@ -195,6 +185,21 @@ func isPopUpPage(id int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetMenuButton sets the page's menu control
|
||||
func (page *BasePage) SetMenuButton(mb *MenuButton) {
|
||||
page.menuButton = mb
|
||||
}
|
||||
|
||||
// GetMenuButton returns the page's menu control
|
||||
func (page *BasePage) GetMenuButton() *MenuButton {
|
||||
return page.menuButton
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *BasePage) GetConfiguredValue() string {
|
||||
return "Unknown value"
|
||||
}
|
||||
|
||||
// GetConfigDefinition is a stub implementation
|
||||
// the real implementation must check with the model and return:
|
||||
// + ConfigDefinedByUser: if the configuration was interactively defined by the user
|
||||
@@ -267,23 +272,17 @@ func (page *BasePage) IsRequired() bool {
|
||||
return page.required
|
||||
}
|
||||
|
||||
// GetButtonPrefix returns string for prefixing a menu button
|
||||
func (page *BasePage) GetButtonPrefix(item Page) string {
|
||||
prefix := MenuButtonPrefixUncompleted
|
||||
// GetMenuStatus returns the menu button status id
|
||||
func GetMenuStatus(item Page) int {
|
||||
res := MenuButtonStatusDefault
|
||||
|
||||
if item.GetDone() {
|
||||
prefix = MenuButtonPrefixCompletedByUser
|
||||
res = MenuButtonStatusUserDefined
|
||||
} else if item.GetConfigDefinition() == ConfigDefinedByConfig {
|
||||
prefix = MenuButtonPrefixCompletedByConfig
|
||||
res = MenuButtonStatusAutoDetect
|
||||
}
|
||||
|
||||
itemType := reflect.TypeOf(item).Elem().Name()
|
||||
|
||||
if strings.Contains(itemType, "SubMenu") {
|
||||
prefix = MenuButtonPrefixSubMenu
|
||||
}
|
||||
|
||||
return prefix
|
||||
return res
|
||||
}
|
||||
|
||||
func (page *BasePage) setupMenu(tui *Tui, id int, menuTitle string, btns int, returnID int) {
|
||||
@@ -295,19 +294,19 @@ func (page *BasePage) setup(tui *Tui, id int, btns int, returnID int) {
|
||||
page.action = ActionNone
|
||||
page.id = id
|
||||
page.tui = tui
|
||||
|
||||
page.newWindow()
|
||||
page.window.SetPack(clui.Vertical)
|
||||
|
||||
page.mFrame = clui.CreateFrame(page.window, 78, 22, BorderNone, clui.Fixed)
|
||||
page.mFrame.SetPack(clui.Vertical)
|
||||
|
||||
page.content = clui.CreateFrame(page.mFrame, 8, 21, BorderNone, clui.Fixed)
|
||||
page.content = clui.CreateFrame(page.window, AutoSize, ContentHeight,
|
||||
BorderNone, clui.Fixed)
|
||||
page.content.SetPack(clui.Vertical)
|
||||
page.content.SetPaddings(2, 1)
|
||||
|
||||
page.cFrame = clui.CreateFrame(page.mFrame, AutoSize, 1, BorderNone, Fixed)
|
||||
page.cFrame = clui.CreateFrame(page.window, AutoSize, 1, BorderNone, Fixed)
|
||||
page.cFrame.SetPack(clui.Horizontal)
|
||||
page.cFrame.SetGaps(1, 1)
|
||||
page.cFrame.SetPaddings(2, 0)
|
||||
page.cFrame.SetPaddings(3, 0)
|
||||
|
||||
if btns&CancelButton == CancelButton {
|
||||
page.newCancelButton(returnID)
|
||||
@@ -321,6 +320,12 @@ func (page *BasePage) setup(tui *Tui, id int, btns int, returnID int) {
|
||||
page.newDoneButton(tui, returnID)
|
||||
}
|
||||
|
||||
frm := clui.CreateFrame(page.window, AutoSize, 1, BorderNone, Fixed)
|
||||
frm.SetPaddings(3, 1)
|
||||
|
||||
clui.CreateLabel(frm, AutoSize, 1,
|
||||
"Use [Tab] or the arrow keys [up and down] to navigate", Fixed)
|
||||
|
||||
page.window.SetVisible(false)
|
||||
}
|
||||
|
||||
@@ -330,7 +335,9 @@ func (page *BasePage) newWindow() {
|
||||
x := (sw - WindowWidth) / 2
|
||||
y := (sh - WindowHeight) / 2
|
||||
|
||||
page.window = clui.AddWindow(x, y, WindowWidth, WindowHeight, " [Clear Linux Installer ("+model.Version+")] ")
|
||||
page.window = clui.AddWindow(x, y, WindowWidth, WindowHeight,
|
||||
" [Clear Linux Installer ("+model.Version+")] ")
|
||||
|
||||
page.window.SetTitleButtons(0)
|
||||
page.window.SetSizable(false)
|
||||
page.window.SetMovable(false)
|
||||
@@ -342,6 +349,7 @@ func (page *BasePage) newWindow() {
|
||||
y := (evt.Height - wh) / 2
|
||||
|
||||
page.window.SetPos(x, y)
|
||||
page.window.ResizeChildren()
|
||||
page.window.PlaceChildren()
|
||||
})
|
||||
}
|
||||
|
||||
25
tui/disk.go
25
tui/disk.go
@@ -5,6 +5,8 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/clearlinux/clr-installer/storage"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
@@ -20,6 +22,29 @@ const (
|
||||
can manually set your own.`
|
||||
)
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *DiskMenuPage) GetConfiguredValue() string {
|
||||
if len(page.getModel().TargetMedias) == 0 {
|
||||
return "No media configured"
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
|
||||
for _, curr := range page.getModel().TargetMedias {
|
||||
for _, part := range curr.Children {
|
||||
tks := []string{part.Name, part.FsType}
|
||||
|
||||
if part.MountPoint != "" {
|
||||
tks = append(tks, part.MountPoint)
|
||||
}
|
||||
|
||||
res = append(res, strings.Join(tks, ":"))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *DiskMenuPage) GetConfigDefinition() int {
|
||||
|
||||
@@ -20,6 +20,17 @@ type HostnamePage struct {
|
||||
userDefined bool
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *HostnamePage) GetConfiguredValue() string {
|
||||
hn := page.getModel().Hostname
|
||||
|
||||
if hn == "" {
|
||||
return "No target system hostname assigned"
|
||||
}
|
||||
|
||||
return hn
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *HostnamePage) GetConfigDefinition() int {
|
||||
@@ -50,7 +61,7 @@ func (page *HostnamePage) setConfirmButton() {
|
||||
|
||||
func newHostnamePage(tui *Tui) (Page, error) {
|
||||
page := &HostnamePage{}
|
||||
page.setupMenu(tui, TuiPageHostname, "Assign Hostname", NoButtons, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageHostname, "Assign Hostname", NoButtons, TuiPageMenu)
|
||||
|
||||
clui.CreateLabel(page.content, 2, 2, "Assign a Hostname for the installation target", Fixed)
|
||||
|
||||
@@ -89,7 +100,7 @@ func newHostnamePage(tui *Tui) (Page, error) {
|
||||
|
||||
page.cancelBtn = CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
page.cancelBtn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
page.confirmBtn = CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Confirm", Fixed)
|
||||
@@ -105,7 +116,7 @@ func newHostnamePage(tui *Tui) (Page, error) {
|
||||
}
|
||||
page.getModel().Hostname = hostname
|
||||
page.setConfirmButton()
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
page.confirmBtn.SetEnabled(false)
|
||||
|
||||
|
||||
@@ -14,6 +14,17 @@ type KernelCMDLine struct {
|
||||
kernelCMDLineEdit *clui.EditField
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (pp *KernelCMDLine) GetConfiguredValue() string {
|
||||
cmdLine := pp.getModel().KernelCMDLine
|
||||
|
||||
if cmdLine == "" {
|
||||
return "No kernel command line append set"
|
||||
}
|
||||
|
||||
return cmdLine
|
||||
}
|
||||
|
||||
// Activate sets the kernel cmd line configuration with the current model's value
|
||||
func (pp *KernelCMDLine) Activate() {
|
||||
pp.kernelCMDLineEdit.SetTitle(pp.getModel().KernelCMDLine)
|
||||
@@ -21,7 +32,7 @@ func (pp *KernelCMDLine) Activate() {
|
||||
|
||||
func newKernelCMDLine(tui *Tui) (Page, error) {
|
||||
page := &KernelCMDLine{}
|
||||
page.setupMenu(tui, TuiPageKernelCMDLine, "Kernel Command Line", NoButtons, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageKernelCMDLine, "Kernel Command Line", NoButtons, TuiPageMenu)
|
||||
|
||||
clui.CreateLabel(page.content, 2, 2, "Configure the Kernel Command Line", Fixed)
|
||||
|
||||
@@ -49,7 +60,7 @@ func newKernelCMDLine(tui *Tui) (Page, error) {
|
||||
|
||||
cancelBtn := CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
cancelBtn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
confirmBtn := CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Confirm", Fixed)
|
||||
@@ -57,7 +68,7 @@ func newKernelCMDLine(tui *Tui) (Page, error) {
|
||||
confirmBtn.OnClick(func(ev clui.Event) {
|
||||
page.getModel().KernelCMDLine = page.kernelCMDLineEdit.Title()
|
||||
page.SetDone(page.kernelCMDLineEdit.Title() != "")
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
page.activated = page.kernelCMDLineEdit
|
||||
|
||||
@@ -24,6 +24,11 @@ type KernelRadio struct {
|
||||
radio *clui.Radio
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (kp *KernelPage) GetConfiguredValue() string {
|
||||
return kp.getModel().Kernel.Bundle
|
||||
}
|
||||
|
||||
// Activate marks selects the kernel radio based on the data model
|
||||
func (kp *KernelPage) Activate() {
|
||||
model := kp.getModel()
|
||||
@@ -64,7 +69,7 @@ func newKernelPage(tui *Tui) (Page, error) {
|
||||
page.kernels = append(page.kernels, &KernelRadio{curr, nil})
|
||||
}
|
||||
|
||||
page.setupMenu(tui, TuiPageKernel, "Kernel Selection", NoButtons, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageKernel, "Kernel Selection", NoButtons, TuiPageMenu)
|
||||
clui.CreateLabel(page.content, 2, 2, "Select desired kernel", Fixed)
|
||||
|
||||
frm := clui.CreateFrame(page.content, AutoSize, AutoSize, BorderNone, Fixed)
|
||||
@@ -88,7 +93,7 @@ func newKernelPage(tui *Tui) (Page, error) {
|
||||
|
||||
cancelBtn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
cancelBtn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
confirmBtn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Confirm", Fixed)
|
||||
@@ -96,7 +101,7 @@ func newKernelPage(tui *Tui) (Page, error) {
|
||||
selected := page.group.Selected()
|
||||
page.getModel().Kernel = page.kernels[selected].kernel
|
||||
page.SetDone(true)
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
return page, nil
|
||||
|
||||
@@ -17,6 +17,11 @@ type KeyboardPage struct {
|
||||
kbdListBox *clui.ListBox
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently keyboard set
|
||||
func (page *KeyboardPage) GetConfiguredValue() string {
|
||||
return page.getModel().Keyboard.Code
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *KeyboardPage) GetConfigDefinition() int {
|
||||
@@ -107,11 +112,12 @@ func newKeyboardPage(tui *Tui) (Page, error) {
|
||||
}
|
||||
})
|
||||
|
||||
frame := clui.CreateFrame(page.content, AutoSize, 7, BorderNone, Fixed)
|
||||
frame := clui.CreateFrame(page.content, AutoSize, AutoSize, BorderNone, Fixed)
|
||||
frame.SetPack(clui.Vertical)
|
||||
frame.SetPaddings(0, 1)
|
||||
|
||||
clui.CreateLabel(frame, AutoSize, 1, "Test keyboard", Fixed)
|
||||
lbl = clui.CreateLabel(frame, AutoSize, 1, "Test keyboard", Fixed)
|
||||
lbl.SetPaddings(0, 1)
|
||||
|
||||
newEditField(frame, false, nil)
|
||||
|
||||
page.activated = page.doneBtn
|
||||
|
||||
@@ -17,6 +17,11 @@ type LanguagePage struct {
|
||||
langListBox *clui.ListBox
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently language set
|
||||
func (page *LanguagePage) GetConfiguredValue() string {
|
||||
return page.getModel().Language.String()
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *LanguagePage) GetConfigDefinition() int {
|
||||
@@ -73,7 +78,7 @@ func newLanguagePage(tui *Tui) (Page, error) {
|
||||
lbl := clui.CreateLabel(page.content, 2, 2, "Select System Language", Fixed)
|
||||
lbl.SetPaddings(0, 2)
|
||||
|
||||
page.langListBox = clui.CreateListBox(page.content, AutoSize, 17, Fixed)
|
||||
page.langListBox = clui.CreateListBox(page.content, AutoSize, ContentHeight-1, Fixed)
|
||||
|
||||
defLanguage := 0
|
||||
for idx, curr := range page.avLanguages {
|
||||
|
||||
111
tui/menu.go
111
tui/menu.go
@@ -5,74 +5,95 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
|
||||
"github.com/clearlinux/clr-installer/controller"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
)
|
||||
|
||||
// MenuPage is the Page implementation for the main menu page
|
||||
type MenuPage struct {
|
||||
BasePage
|
||||
btns []*SimpleButton
|
||||
installBtn *SimpleButton
|
||||
tabGroup *TabGroup
|
||||
reqTab *TabPage
|
||||
advTab *TabPage
|
||||
}
|
||||
|
||||
func (page *MenuPage) addMenuItem(item Page) bool {
|
||||
func (page *MenuPage) addMenuItem(item Page, tab *TabPage) *MenuButton {
|
||||
fw, _ := tab.frame.Size()
|
||||
|
||||
buttonPrefix := page.GetButtonPrefix(item)
|
||||
title := fmt.Sprintf(" %s %s", buttonPrefix, item.GetMenuTitle())
|
||||
btn := CreateSimpleButton(page.content, AutoSize, AutoSize, title, Fixed)
|
||||
btn.SetStyle("Menu")
|
||||
btn := CreateMenuButton(tab.frame, MenuButtonStatusDefault, item.GetMenuTitle(), fw)
|
||||
btn.SetStyle("Main")
|
||||
btn.SetAlign(AlignLeft)
|
||||
btn.SetActive(false)
|
||||
|
||||
item.SetMenuButton(btn)
|
||||
|
||||
btn.OnClick(func(ev clui.Event) {
|
||||
tab.activeMenu = btn
|
||||
page.GotoPage(item.GetID())
|
||||
})
|
||||
|
||||
page.btns = append(page.btns, btn)
|
||||
|
||||
return buttonPrefix != MenuButtonPrefixUncompleted
|
||||
return btn
|
||||
}
|
||||
|
||||
// Activate is called when the page is "shown" and it repaints the main menu based on the
|
||||
// available menu pages and their done/undone status
|
||||
func (page *MenuPage) Activate() {
|
||||
for _, curr := range page.btns {
|
||||
curr.Destroy()
|
||||
}
|
||||
page.btns = []*SimpleButton{}
|
||||
|
||||
previous := false
|
||||
activeSet := false
|
||||
|
||||
// if we're returning to the "advanced" tab then simply sets the previously
|
||||
// active menu item
|
||||
if page.advTab.IsVisible() {
|
||||
page.activated = page.advTab.activeMenu
|
||||
activeSet = true
|
||||
}
|
||||
|
||||
// if we're returning to the "required" tab then we iterate over not yet
|
||||
// completed required "tasks" and select the missing one
|
||||
for _, curr := range page.tui.pages {
|
||||
// Skip Menu Pages that are not required
|
||||
if !curr.IsRequired() {
|
||||
if curr.GetMenuTitle() == "" || curr.GetID() == page.GetID() {
|
||||
continue
|
||||
}
|
||||
|
||||
tab := page.reqTab
|
||||
|
||||
if !curr.IsRequired() {
|
||||
tab = page.advTab
|
||||
}
|
||||
|
||||
if page.tui.prevPage != nil {
|
||||
// Is this menu option match the previous page?
|
||||
previous = page.tui.prevPage.GetID() == curr.GetID()
|
||||
}
|
||||
|
||||
btn := curr.GetMenuButton()
|
||||
|
||||
if btn == nil {
|
||||
btn = page.addMenuItem(curr, tab)
|
||||
}
|
||||
|
||||
btn.SetMenuItemValue(curr.GetConfiguredValue())
|
||||
btn.SetStatus(GetMenuStatus(curr))
|
||||
|
||||
// Does the menu item added have the data set completed?
|
||||
completed := page.addMenuItem(curr)
|
||||
completed := GetMenuStatus(curr) != MenuButtonStatusDefault
|
||||
|
||||
// If we haven't found the first active choice, set it
|
||||
if !activeSet && !completed {
|
||||
// Make last button added Active
|
||||
page.activated = page.btns[len(page.btns)-1]
|
||||
page.activated = btn
|
||||
activeSet = true
|
||||
}
|
||||
|
||||
// Special case if the previous page and the data set is not completed
|
||||
// we want THIS to be the active choice for easy return
|
||||
if previous && !completed {
|
||||
if previous && !completed && !activeSet {
|
||||
// Make last button added Active
|
||||
page.activated = page.btns[len(page.btns)-1]
|
||||
page.activated = btn
|
||||
activeSet = true
|
||||
}
|
||||
}
|
||||
@@ -80,22 +101,50 @@ func (page *MenuPage) Activate() {
|
||||
if page.getModel() != nil && page.getModel().Validate() == nil {
|
||||
page.installBtn.SetEnabled(true)
|
||||
page.activated = page.installBtn
|
||||
} else {
|
||||
scrollTabToActive(page.activated, page.tabGroup)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
menuHelp = `Choose the next steps. Use <Tab> or arrow keys (up and down) to navigate
|
||||
between the elements.
|
||||
`
|
||||
)
|
||||
func scrollTabToActive(activated clui.Control, group *TabGroup) {
|
||||
if activated == nil {
|
||||
return
|
||||
}
|
||||
|
||||
vFrame := group.GetVisibleFrame()
|
||||
|
||||
_, cy, _, ch := vFrame.Clipper()
|
||||
vx, vy := vFrame.Pos()
|
||||
|
||||
_, ay := activated.Pos()
|
||||
_, ah := activated.Size()
|
||||
|
||||
if ay+ah > cy+ch || ay < cy {
|
||||
diff := (cy + ch) - (ay + ah)
|
||||
ty := vy + diff
|
||||
vFrame.ScrollTo(vx, ty)
|
||||
}
|
||||
}
|
||||
|
||||
func newMenuPage(tui *Tui) (Page, error) {
|
||||
var err error
|
||||
|
||||
page := &MenuPage{}
|
||||
page.setup(tui, TuiPageMenu, NoButtons, TuiPageMenu)
|
||||
|
||||
lbl := clui.CreateLabel(page.content, 2, 3, menuHelp, Fixed)
|
||||
lbl.SetMultiline(true)
|
||||
lbl.SetPaddings(0, 2)
|
||||
// the menu is an special case, we have no paddings
|
||||
page.content.SetPaddings(0, 0)
|
||||
|
||||
page.tabGroup = NewTabGroup(page.content, 1, ContentHeight)
|
||||
page.reqTab, err = page.tabGroup.AddTab("Required options", 'r')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
page.advTab, err = page.tabGroup.AddTab("Advanced options", 'a')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cancelBtn := CreateSimpleButton(page.cFrame, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
cancelBtn.OnClick(func(ev clui.Event) {
|
||||
|
||||
233
tui/menu_button.go
Normal file
233
tui/menu_button.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright © 2018 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
xs "github.com/huandu/xstrings"
|
||||
term "github.com/nsf/termbox-go"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MenuButton is the implementation of a clui button with simpler
|
||||
// visual elements (i.e no shadows).
|
||||
type MenuButton struct {
|
||||
clui.BaseControl
|
||||
itemValue string
|
||||
pressed int32
|
||||
onClick func(clui.Event)
|
||||
status int
|
||||
sWdith int // splitter width
|
||||
}
|
||||
|
||||
const (
|
||||
textPadding = 3
|
||||
|
||||
// MenuButtonStatusDefault means: not the auto detect,
|
||||
// not the user defined, not even failed
|
||||
MenuButtonStatusDefault = iota
|
||||
|
||||
// MenuButtonStatusAutoDetect means: the current value was autodetected
|
||||
MenuButtonStatusAutoDetect
|
||||
|
||||
// MenuButtonStatusUserDefined means: the user actively changed the value
|
||||
MenuButtonStatusUserDefined
|
||||
|
||||
//MenuButtonStatusFailure means: the label displayed is an error message
|
||||
MenuButtonStatusFailure
|
||||
)
|
||||
|
||||
var (
|
||||
// by now we're using the same char for everything
|
||||
statusSymbol = map[int]rune{
|
||||
MenuButtonStatusDefault: '»',
|
||||
MenuButtonStatusUserDefined: '»',
|
||||
MenuButtonStatusFailure: '»',
|
||||
MenuButtonStatusAutoDetect: '»',
|
||||
}
|
||||
)
|
||||
|
||||
// SetStatus sets the status attribute for a menu button
|
||||
func (mb *MenuButton) SetStatus(status int) {
|
||||
mb.status = status
|
||||
}
|
||||
|
||||
// Status returns the currently set status for a menu button
|
||||
func (mb *MenuButton) Status() int {
|
||||
return mb.status
|
||||
}
|
||||
|
||||
// SetMenuItemValue sets the value for the itemValue (configured value for a menu item)
|
||||
func (mb *MenuButton) SetMenuItemValue(sv string) {
|
||||
mb.itemValue = sv
|
||||
}
|
||||
|
||||
// MenuItemValue returns the value set for itemValue (configured value for a menu item)
|
||||
func (mb *MenuButton) MenuItemValue() string {
|
||||
return mb.itemValue
|
||||
}
|
||||
|
||||
// CreateMenuButton returns an instance of MenuButton
|
||||
// parent - is the parent control this button is attached to
|
||||
// status - one of: MenuButtonStatusDefault, MenuButtonStatusUserDefined, MenuButtonStatusFailure
|
||||
// title - the button's label
|
||||
func CreateMenuButton(parent clui.Control, status int, title string, sWidth int) *MenuButton {
|
||||
mb := new(MenuButton)
|
||||
mb.sWdith = sWidth
|
||||
mb.BaseControl = clui.NewBaseControl()
|
||||
|
||||
mb.SetParent(parent)
|
||||
mb.SetAlign(clui.AlignLeft)
|
||||
mb.status = status
|
||||
|
||||
height := 4
|
||||
width := xs.Len(title) + 2
|
||||
|
||||
mb.SetTitle(title)
|
||||
|
||||
mb.SetSize(width, height)
|
||||
|
||||
mb.SetConstraints(width, height)
|
||||
mb.SetScale(Fixed)
|
||||
|
||||
if parent != nil {
|
||||
parent.AddChild(mb)
|
||||
}
|
||||
|
||||
return mb
|
||||
}
|
||||
|
||||
// Draw paints the button in the screen and adjust colors depending on the button state
|
||||
func (mb *MenuButton) Draw() {
|
||||
|
||||
clui.PushAttributes()
|
||||
defer clui.PopAttributes()
|
||||
|
||||
x, y := mb.Pos()
|
||||
w, h := mb.Size()
|
||||
|
||||
fg, bg := mb.TextColor(), mb.BackColor()
|
||||
sfg, sbg := mb.TextColor(), mb.BackColor()
|
||||
|
||||
if mb.Active() {
|
||||
fgActive, bgActive := mb.ActiveColors()
|
||||
|
||||
fg = clui.RealColor(fgActive, mb.Style(), "MenuActiveText")
|
||||
bg = clui.RealColor(bgActive, mb.Style(), "MenuActiveBack")
|
||||
|
||||
sfg = clui.RealColor(sfg, mb.Style(), "MenuContentActiveText")
|
||||
sbg = clui.RealColor(sbg, mb.Style(), "MenuContentActiveBack")
|
||||
} else {
|
||||
fg = clui.RealColor(fg, mb.Style(), "MenuText")
|
||||
bg = clui.RealColor(bg, mb.Style(), "MenuBack")
|
||||
|
||||
sfg = clui.RealColor(sfg, mb.Style(), "MenuContentText")
|
||||
sbg = clui.RealColor(sbg, mb.Style(), "MenuContentBack")
|
||||
}
|
||||
|
||||
clui.SetTextColor(fg)
|
||||
|
||||
shift, text := clui.AlignColorizedText(mb.Title(), w, mb.Align())
|
||||
|
||||
clui.SetBackColor(bg)
|
||||
clui.FillRect(x, y, w, h, ' ')
|
||||
clui.DrawText(x+shift+textPadding, y+1, text)
|
||||
|
||||
clui.PopAttributes()
|
||||
clui.PushAttributes()
|
||||
|
||||
itemValue := fmt.Sprintf("%c %s", statusSymbol[mb.status], mb.itemValue)
|
||||
shift, itemText := clui.AlignColorizedText(itemValue, w, mb.Align())
|
||||
|
||||
clui.SetBackColor(sbg)
|
||||
clui.SetTextColor(sfg)
|
||||
clui.DrawText(x+shift+textPadding, y+2, itemText)
|
||||
|
||||
if !mb.Active() {
|
||||
for i := 0; i < mb.sWdith; i++ {
|
||||
clui.DrawText(x+shift+textPadding+i, y+3, "_")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mb *MenuButton) isPressed() int32 {
|
||||
return atomic.LoadInt32(&mb.pressed)
|
||||
}
|
||||
|
||||
func (mb *MenuButton) setPressed(pressed int32) {
|
||||
atomic.StoreInt32(&mb.pressed, pressed)
|
||||
}
|
||||
|
||||
func (mb *MenuButton) processKeyEvent(event clui.Event) bool {
|
||||
ekey := event.Key
|
||||
|
||||
if (ekey == term.KeySpace || ekey == term.KeyEnter) && mb.isPressed() == 0 {
|
||||
mb.setPressed(1)
|
||||
ev := clui.Event{Type: clui.EventRedraw}
|
||||
|
||||
go func() {
|
||||
clui.PutEvent(ev)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
mb.setPressed(0)
|
||||
clui.PutEvent(ev)
|
||||
}()
|
||||
|
||||
if mb.onClick != nil {
|
||||
mb.onClick(event)
|
||||
}
|
||||
return true
|
||||
} else if ekey == term.KeyEsc && mb.isPressed() != 0 {
|
||||
mb.setPressed(0)
|
||||
clui.ReleaseEvents()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (mb *MenuButton) processMouseEvent(event clui.Event) bool {
|
||||
if event.Key == term.MouseLeft {
|
||||
mb.setPressed(1)
|
||||
clui.GrabEvents(mb)
|
||||
return true
|
||||
} else if event.Key == term.MouseRelease && mb.isPressed() != 0 {
|
||||
clui.ReleaseEvents()
|
||||
bX, bY := mb.Pos()
|
||||
bw, bh := mb.Size()
|
||||
|
||||
if event.X >= bX && event.Y >= bY && event.X < bX+bw && event.Y < bY+bh {
|
||||
if mb.onClick != nil {
|
||||
mb.onClick(event)
|
||||
}
|
||||
}
|
||||
mb.setPressed(0)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ProcessEvent will process the events triggered by clui mainloop
|
||||
func (mb *MenuButton) ProcessEvent(event clui.Event) bool {
|
||||
if !mb.Enabled() {
|
||||
return false
|
||||
}
|
||||
|
||||
if event.Type == clui.EventKey {
|
||||
return mb.processKeyEvent(event)
|
||||
} else if event.Type == clui.EventMouse {
|
||||
return mb.processMouseEvent(event)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// OnClick sets the button's onClick callback
|
||||
func (mb *MenuButton) OnClick(fn func(clui.Event)) {
|
||||
mb.onClick = fn
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/clearlinux/clr-installer/network"
|
||||
|
||||
@@ -21,6 +22,34 @@ type NetworkPage struct {
|
||||
interfaces []*network.Interface
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *NetworkPage) GetConfiguredValue() string {
|
||||
var err error
|
||||
ifaces := page.getModel().NetworkInterfaces
|
||||
|
||||
if len(ifaces) == 0 {
|
||||
ifaces, err = network.Interfaces()
|
||||
if err != nil {
|
||||
return "Could not load network interfaces"
|
||||
}
|
||||
}
|
||||
|
||||
res := []string{}
|
||||
|
||||
for _, curr := range ifaces {
|
||||
for _, addr := range curr.Addrs {
|
||||
if addr.Version != network.IPv4 {
|
||||
continue
|
||||
}
|
||||
|
||||
tks := []string{addr.IP, addr.NetMask}
|
||||
res = append(res, strings.Join(tks, ":"))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *NetworkPage) GetConfigDefinition() int {
|
||||
@@ -99,7 +128,7 @@ func (page *NetworkPage) Activate() {
|
||||
func newNetworkPage(tui *Tui) (Page, error) {
|
||||
page := &NetworkPage{}
|
||||
page.setupMenu(tui, TuiPageNetwork, "Configure network interfaces",
|
||||
BackButton, TuiPageAdvancedMenu)
|
||||
BackButton, TuiPageMenu)
|
||||
|
||||
page.frm = clui.CreateFrame(page.content, AutoSize, AutoSize, BorderNone, Fixed)
|
||||
page.frm.SetPack(clui.Vertical)
|
||||
|
||||
@@ -157,7 +157,7 @@ func validateIPEdit(k term.Key, ch rune) bool {
|
||||
|
||||
func newNetworkInterfacePage(tui *Tui) (Page, error) {
|
||||
page := &NetworkInterfacePage{}
|
||||
page.setup(tui, TuiPageInterface, NoButtons, TuiPageAdvancedMenu)
|
||||
page.setup(tui, TuiPageInterface, NoButtons, TuiPageMenu)
|
||||
|
||||
frm := clui.CreateFrame(page.content, AutoSize, AutoSize, BorderNone, Fixed)
|
||||
frm.SetPack(clui.Horizontal)
|
||||
|
||||
@@ -16,12 +16,13 @@ import (
|
||||
// the Page interface, but does not allocate a Window. Instead it will launch
|
||||
// a modal PopUp window.
|
||||
type NetworkValidatePage struct {
|
||||
tui *Tui // the Tui frontend reference
|
||||
menuTitle string // the title used to show on main menu
|
||||
done bool // marks if an item is completed
|
||||
id int // the page id
|
||||
data interface{} // arbitrary page context data
|
||||
required bool // marks if an item is required for the install
|
||||
tui *Tui // the Tui frontend reference
|
||||
menuTitle string // the title used to show on main menu
|
||||
done bool // marks if an item is completed
|
||||
id int // the page id
|
||||
data interface{} // arbitrary page context data
|
||||
required bool // marks if an item is required for the install
|
||||
menuButton *MenuButton // the menu button reference
|
||||
}
|
||||
|
||||
// GetID returns the current page's identifier
|
||||
@@ -74,7 +75,7 @@ func (page *NetworkValidatePage) GetDone() bool {
|
||||
func (page *NetworkValidatePage) Activate() {
|
||||
if dialog, err := CreateNetworkTestDialogBox(page.tui.model); err == nil {
|
||||
dialog.OnClose(func() {
|
||||
page.tui.gotoPage(TuiPageAdvancedMenu, page.tui.currPage)
|
||||
page.tui.gotoPage(TuiPageMenu, page.tui.currPage)
|
||||
})
|
||||
|
||||
result := dialog.RunNetworkTest()
|
||||
@@ -100,15 +101,24 @@ func (page *NetworkValidatePage) GetConfigDefinition() int {
|
||||
return ConfigDefinedByUser
|
||||
}
|
||||
|
||||
// GetButtonPrefix returns string for prefixing a menu button
|
||||
func (page *NetworkValidatePage) GetButtonPrefix(item Page) string {
|
||||
prefix := MenuButtonPrefixUncompleted
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *NetworkValidatePage) GetConfiguredValue() string {
|
||||
return "Check if all the required network settings are properly set"
|
||||
}
|
||||
|
||||
if item.GetDone() {
|
||||
prefix = MenuButtonPrefixCompletedByUser
|
||||
}
|
||||
// GetMenuStatus returns the menu button status id
|
||||
func (page *NetworkValidatePage) GetMenuStatus(item Page) int {
|
||||
return MenuButtonStatusDefault
|
||||
}
|
||||
|
||||
return prefix
|
||||
// GetMenuButton is a page implementation for network validate popup
|
||||
func (page *NetworkValidatePage) GetMenuButton() *MenuButton {
|
||||
return page.menuButton
|
||||
}
|
||||
|
||||
// SetMenuButton is a no-op page implementation for network validate popup
|
||||
func (page *NetworkValidatePage) SetMenuButton(mb *MenuButton) {
|
||||
page.menuButton = mb
|
||||
}
|
||||
|
||||
func newNetworkValidatePage(tui *Tui) (Page, error) {
|
||||
|
||||
17
tui/proxy.go
17
tui/proxy.go
@@ -19,6 +19,17 @@ type ProxyPage struct {
|
||||
confirmBtn *SimpleButton
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (pp *ProxyPage) GetConfiguredValue() string {
|
||||
value := pp.getModel().HTTPSProxy
|
||||
|
||||
if value == "" {
|
||||
return "No HTTPS proxy URL set"
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
// Activate sets the https proxy with the current model's value
|
||||
func (pp *ProxyPage) Activate() {
|
||||
pp.httpsProxyEdit.SetTitle(pp.getModel().HTTPSProxy)
|
||||
@@ -35,7 +46,7 @@ func (pp *ProxyPage) setConfirmButton() {
|
||||
|
||||
func newProxyPage(tui *Tui) (Page, error) {
|
||||
page := &ProxyPage{}
|
||||
page.setupMenu(tui, TuiPageProxy, "Proxy", NoButtons, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageProxy, "Proxy", NoButtons, TuiPageMenu)
|
||||
|
||||
clui.CreateLabel(page.content, 2, 2, "Configure the network proxy", Fixed)
|
||||
|
||||
@@ -82,7 +93,7 @@ func newProxyPage(tui *Tui) (Page, error) {
|
||||
|
||||
cancelBtn := CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
cancelBtn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
page.confirmBtn = CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Confirm", Fixed)
|
||||
@@ -92,7 +103,7 @@ func newProxyPage(tui *Tui) (Page, error) {
|
||||
page.getModel().HTTPSProxy = proxy
|
||||
if dialog, err := CreateNetworkTestDialogBox(page.tui.model); err == nil {
|
||||
dialog.OnClose(func() {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
if dialog.RunNetworkTest() {
|
||||
page.SetDone(proxy != "")
|
||||
|
||||
@@ -16,8 +16,9 @@ import (
|
||||
// visual elements (i.e no shadows).
|
||||
type SimpleButton struct {
|
||||
clui.BaseControl
|
||||
pressed int32
|
||||
onClick func(clui.Event)
|
||||
pressed int32
|
||||
onClick func(clui.Event)
|
||||
forceActiveStyle bool
|
||||
}
|
||||
|
||||
// CreateSimpleButton returns an instance of SimpleButton
|
||||
@@ -70,7 +71,7 @@ func (b *SimpleButton) Draw() {
|
||||
if !b.Enabled() {
|
||||
fg = clui.RealColor(fg, b.Style(), "ButtonDisabledText")
|
||||
bg = clui.RealColor(bg, b.Style(), "ButtonDisabledBack")
|
||||
} else if b.Active() {
|
||||
} else if b.Active() || b.ForceActiveStyle() {
|
||||
fgActive, bgActive := b.ActiveColors()
|
||||
|
||||
fg = clui.RealColor(fgActive, b.Style(), "ButtonActiveText")
|
||||
@@ -171,3 +172,14 @@ func (b *SimpleButton) ProcessEvent(event clui.Event) bool {
|
||||
func (b *SimpleButton) OnClick(fn func(clui.Event)) {
|
||||
b.onClick = fn
|
||||
}
|
||||
|
||||
// SetForceActiveStyle even if the button is not active the user may want
|
||||
// to force setting the active style
|
||||
func (b *SimpleButton) SetForceActiveStyle(f bool) {
|
||||
b.forceActiveStyle = f
|
||||
}
|
||||
|
||||
// ForceActiveStyle returns the forceActiveStyle flag
|
||||
func (b *SimpleButton) ForceActiveStyle() bool {
|
||||
return b.forceActiveStyle
|
||||
}
|
||||
|
||||
@@ -22,6 +22,17 @@ type SwupdMirrorPage struct {
|
||||
userDefined bool
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *SwupdMirrorPage) GetConfiguredValue() string {
|
||||
mirror := page.getModel().SwupdMirror
|
||||
|
||||
if mirror == "" {
|
||||
return "No swupd mirror set"
|
||||
}
|
||||
|
||||
return mirror
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *SwupdMirrorPage) GetConfigDefinition() int {
|
||||
@@ -52,7 +63,7 @@ func (page *SwupdMirrorPage) setConfirmButton() {
|
||||
|
||||
func newSwupdMirrorPage(tui *Tui) (Page, error) {
|
||||
page := &SwupdMirrorPage{}
|
||||
page.setupMenu(tui, TuiPageSwupdMirror, "Swupd Mirror", NoButtons, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageSwupdMirror, "Swupd Mirror", NoButtons, TuiPageMenu)
|
||||
|
||||
clui.CreateLabel(page.content, 2, 2, "Configure the Installation Source (swupd) Mirror", Fixed)
|
||||
|
||||
@@ -101,7 +112,7 @@ func newSwupdMirrorPage(tui *Tui) (Page, error) {
|
||||
|
||||
page.cancelBtn = CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Cancel", Fixed)
|
||||
page.cancelBtn.OnClick(func(ev clui.Event) {
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
})
|
||||
|
||||
page.confirmBtn = CreateSimpleButton(btnFrm, AutoSize, AutoSize, "Confirm", Fixed)
|
||||
@@ -114,7 +125,7 @@ func newSwupdMirrorPage(tui *Tui) (Page, error) {
|
||||
page.getModel().SwupdMirror = mirror
|
||||
}
|
||||
page.SetDone(false)
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
page.userDefined = false
|
||||
} else {
|
||||
url, err := swupd.SetHostMirror(mirror)
|
||||
@@ -127,7 +138,7 @@ func newSwupdMirrorPage(tui *Tui) (Page, error) {
|
||||
page.userDefined = true
|
||||
page.getModel().SwupdMirror = mirror
|
||||
page.SetDone(true)
|
||||
page.GotoPage(TuiPageAdvancedMenu)
|
||||
page.GotoPage(TuiPageMenu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
208
tui/tab.go
Normal file
208
tui/tab.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
"github.com/clearlinux/clr-installer/errors"
|
||||
)
|
||||
|
||||
// TabGroup represents a tab group and holds logically grouped tabs
|
||||
type TabGroup struct {
|
||||
mainFrame clui.Control // the widget content frame
|
||||
btnsFrame clui.Control // this frame holds the buttons elements of the tab component
|
||||
contentFrame clui.Control // where the contents are stacked
|
||||
pages []*TabPage // represents the pages - each page contains a button and its content frame
|
||||
selected *TabPage // currently selected page
|
||||
height int // the menu frame height
|
||||
paddingX int // the menu X padding
|
||||
window clui.Control // the window the tab was added to
|
||||
}
|
||||
|
||||
// TabPage represents an individual element containing basically a hotkey, button and content
|
||||
type TabPage struct {
|
||||
hotKey rune // the kotkey char
|
||||
label string // the button label (non formated)
|
||||
btn *SimpleButton // the tab button
|
||||
frame *clui.Frame // the content frame
|
||||
activeMenu clui.Control
|
||||
}
|
||||
|
||||
// TabKeyCb holds the contexts for the keybinds handling iterations
|
||||
type TabKeyCb struct {
|
||||
tab *TabGroup
|
||||
}
|
||||
|
||||
const (
|
||||
tabButtonHeight = 3
|
||||
keyBindingDescWidth = 4
|
||||
tabPadding = 4
|
||||
)
|
||||
|
||||
// NewTabGroup allocates a new TabGroup object and initializes the basic frames and triggers
|
||||
// the events pooling
|
||||
func NewTabGroup(parent clui.Control, paddingX int, height int) *TabGroup {
|
||||
mainFrame := clui.CreateFrame(parent, AutoSize, height, BorderNone, Fixed)
|
||||
mainFrame.SetPack(clui.Vertical)
|
||||
|
||||
btnsFrame := clui.CreateFrame(mainFrame, AutoSize, tabButtonHeight, BorderNone, Fixed)
|
||||
btnsFrame.SetStyle("Tab")
|
||||
|
||||
contentFrame := clui.CreateFrame(mainFrame, AutoSize, height, BorderNone, Fixed)
|
||||
contentFrame.SetPack(clui.Vertical)
|
||||
|
||||
res := &TabGroup{
|
||||
btnsFrame: btnsFrame,
|
||||
mainFrame: mainFrame,
|
||||
contentFrame: contentFrame,
|
||||
height: height,
|
||||
paddingX: paddingX,
|
||||
}
|
||||
|
||||
ctrl := parent
|
||||
for ctrl.Parent() != nil {
|
||||
ctrl = ctrl.Parent()
|
||||
}
|
||||
|
||||
wnd := ctrl.(*clui.Window)
|
||||
wnd.OnKeyDown(keyEventCb, &TabKeyCb{tab: res})
|
||||
|
||||
res.window = wnd
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// AddTab allocates a new TagPage, creates button and frame accordingly
|
||||
func (tg *TabGroup) AddTab(label string, hotKey rune) (*TabPage, error) {
|
||||
for _, curr := range tg.pages {
|
||||
if curr.hotKey == hotKey {
|
||||
return nil, errors.Errorf("Duplicated hotkey between %s and %s", curr.label, label)
|
||||
}
|
||||
}
|
||||
|
||||
width := len(label) + keyBindingDescWidth + tabPadding
|
||||
page := &TabPage{hotKey: hotKey, label: label}
|
||||
|
||||
active := len(tg.pages) == 0
|
||||
tg.pages = append(tg.pages, page)
|
||||
|
||||
flabel := fmt.Sprintf("[%c] %s", unicode.To(unicode.UpperCase, hotKey), label)
|
||||
|
||||
page.btn = CreateSimpleButton(tg.btnsFrame, width, 3, flabel, Fixed)
|
||||
page.btn.SetForceActiveStyle(active)
|
||||
page.btn.SetAlign(AlignLeft)
|
||||
page.btn.SetPaddings(2, 1)
|
||||
page.btn.SetStyle("Tab")
|
||||
page.btn.SetTabStop(false)
|
||||
|
||||
page.btn.OnClick(func(clui.Event) {
|
||||
var hidden []*TabPage
|
||||
|
||||
for _, curr := range tg.pages {
|
||||
if curr == page {
|
||||
continue
|
||||
}
|
||||
|
||||
hidden = append(hidden, curr)
|
||||
}
|
||||
|
||||
tg.setSelected(page, hidden)
|
||||
})
|
||||
|
||||
page.frame = clui.CreateFrame(tg.contentFrame, AutoSize, tg.height, BorderNone, Fixed)
|
||||
page.frame.SetPack(clui.Vertical)
|
||||
page.frame.SetPaddings(tg.paddingX, 1)
|
||||
page.frame.SetScrollable(true)
|
||||
|
||||
if active {
|
||||
tg.selected = page
|
||||
} else {
|
||||
page.frame.SetVisible(false)
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func keyEventCb(ev clui.Event, data interface{}) bool {
|
||||
cbData := data.(*TabKeyCb)
|
||||
|
||||
var selected *TabPage
|
||||
var hidden []*TabPage
|
||||
|
||||
for _, curr := range cbData.tab.pages {
|
||||
if ev.Ch == curr.hotKey || ev.Ch == unicode.ToUpper(curr.hotKey) {
|
||||
selected = curr
|
||||
} else {
|
||||
hidden = append(hidden, curr)
|
||||
}
|
||||
}
|
||||
|
||||
if selected == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cbData.tab.setSelected(selected, hidden)
|
||||
return true
|
||||
}
|
||||
|
||||
func (tg *TabGroup) setSelected(selected *TabPage, hidden []*TabPage) {
|
||||
if selected == nil || tg.selected == selected {
|
||||
return
|
||||
}
|
||||
|
||||
for _, curr := range hidden {
|
||||
if curr.frame != nil {
|
||||
curr.activeMenu = clui.ActiveControl(curr.frame)
|
||||
curr.frame.SetVisible(false)
|
||||
}
|
||||
curr.btn.SetForceActiveStyle(false)
|
||||
}
|
||||
|
||||
selected.btn.SetForceActiveStyle(true)
|
||||
tg.selected = selected
|
||||
|
||||
selected.frame.SetVisible(true)
|
||||
|
||||
activeMenu := selected.activeMenu
|
||||
if activeMenu == nil {
|
||||
for _, curr := range selected.frame.Children() {
|
||||
if _, ok := curr.(*MenuButton); ok {
|
||||
activeMenu = curr
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selected.activeMenu = activeMenu
|
||||
clui.ActivateControl(tg.window, activeMenu)
|
||||
activeMenu.SetActive(true)
|
||||
}
|
||||
|
||||
// SetActive sets the nth page of a TabGroup as active
|
||||
func (tg *TabGroup) SetActive(idx int) error {
|
||||
if idx > len(tg.pages)-1 || idx < 0 {
|
||||
return errors.Errorf("Invalid page index: %d", idx)
|
||||
}
|
||||
|
||||
for i, curr := range tg.pages {
|
||||
curr.btn.SetForceActiveStyle(i == idx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVisibleFrame returns the visible page's content frame
|
||||
func (tg *TabGroup) GetVisibleFrame() *clui.Frame {
|
||||
for _, curr := range tg.pages {
|
||||
if curr.IsVisible() {
|
||||
return curr.frame
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsVisible returns true if a given tab page is visible, and false otherwise
|
||||
func (tp *TabPage) IsVisible() bool {
|
||||
return tp.frame.Visible()
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/VladimirMarkelov/clui"
|
||||
@@ -17,6 +18,23 @@ type TelemetryPage struct {
|
||||
BasePage
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (tp *TelemetryPage) GetConfiguredValue() string {
|
||||
var obs string
|
||||
var res string
|
||||
|
||||
if tp.getModel().Telemetry.Defined {
|
||||
obs = " (Acknowledgment required)"
|
||||
}
|
||||
|
||||
res = "Disabled"
|
||||
if tp.getModel().Telemetry.Enabled {
|
||||
res = "Enabled"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s", res, obs)
|
||||
}
|
||||
|
||||
func newTelemetryPage(tui *Tui) (Page, error) {
|
||||
page := &TelemetryPage{
|
||||
BasePage: BasePage{
|
||||
|
||||
@@ -17,6 +17,11 @@ type TimezonePage struct {
|
||||
tzListBox *clui.ListBox
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently timezone set
|
||||
func (page *TimezonePage) GetConfiguredValue() string {
|
||||
return page.getModel().Timezone.Code
|
||||
}
|
||||
|
||||
// GetConfigDefinition returns if the config was interactively defined by the user,
|
||||
// was loaded from a config file or if the config is not set.
|
||||
func (page *TimezonePage) GetConfigDefinition() int {
|
||||
@@ -73,7 +78,7 @@ func newTimezonePage(tui *Tui) (Page, error) {
|
||||
lbl := clui.CreateLabel(page.content, 2, 2, "Select System Timezone", Fixed)
|
||||
lbl.SetPaddings(0, 2)
|
||||
|
||||
page.tzListBox = clui.CreateListBox(page.content, AutoSize, 17, Fixed)
|
||||
page.tzListBox = clui.CreateListBox(page.content, AutoSize, ContentHeight-1, Fixed)
|
||||
|
||||
defTimezone := 0
|
||||
for idx, curr := range page.avTimezones {
|
||||
|
||||
@@ -128,7 +128,6 @@ func (tui *Tui) Run(md *model.SystemInstall, rootDir string) (bool, error) {
|
||||
{"kernel cmdline", newKernelCMDLine},
|
||||
{"kernel selection", newKernelPage},
|
||||
{"install", newInstallPage},
|
||||
{"advanced menu", newAdvancedPage},
|
||||
{"swupd mirror", newSwupdMirrorPage},
|
||||
{"hostname", newHostnamePage},
|
||||
{"autoupdate", newAutoUpdatePage},
|
||||
|
||||
@@ -6,6 +6,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/clearlinux/clr-installer/user"
|
||||
|
||||
@@ -35,6 +36,28 @@ type UserBtn struct {
|
||||
btn *SimpleButton
|
||||
}
|
||||
|
||||
// GetConfiguredValue Returns the string representation of currently value set
|
||||
func (page *UseraddPage) GetConfiguredValue() string {
|
||||
users := page.getModel().Users
|
||||
res := []string{}
|
||||
|
||||
if len(users) == 0 {
|
||||
return "No users added"
|
||||
}
|
||||
|
||||
for _, curr := range users {
|
||||
tks := []string{curr.Login}
|
||||
|
||||
if curr.Admin {
|
||||
tks = append(tks, "admin")
|
||||
}
|
||||
|
||||
res = append(res, strings.Join(tks, ":"))
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
|
||||
func (page *UseraddPage) validateLogin() {
|
||||
showLabel := false
|
||||
enableConfirm := true
|
||||
@@ -102,7 +125,7 @@ func (page *UseraddPage) validatePassword() {
|
||||
|
||||
func newUseraddPage(tui *Tui) (Page, error) {
|
||||
page := &UseraddPage{users: []*UserBtn{}}
|
||||
page.setupMenu(tui, TuiPageUseradd, "Add Users", BackButton, TuiPageAdvancedMenu)
|
||||
page.setupMenu(tui, TuiPageUseradd, "Add Users", BackButton, TuiPageMenu)
|
||||
|
||||
clui.CreateLabel(page.content, 2, 2, "Add new users", Fixed)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user