Teleprompter#
teleprompter#
Jon Strand’s » teleprompter tutorial
teleprompter with gio version bumped up
gio version bump up:
ref: https://todo.sr.ht/~eliasnaur/gio/550
ref: https://github.com/search?q=repo%3Aegonelbre%2Fexpgio%20filter&type=code
workable revise:
keys (Shift)-F|(Shift)-S|(Shift)-U|(Shift)-D|(Shift)-J|(Shift)-K|(Shift)-W|(Shift)-N|Space are working now.
un-working code:
TODO: pointer.Scroll and key + - not working.
package main
import (
"flag"
"image"
"image/color"
"log"
"os"
"strings"
"time"
"gioui.org/app"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget/material"
)
type C = layout.Context
type D = layout.Dimensions
// Command line input variables
var filename *string
// A []string to hold the speech as a list of paragraphs
var paragraphList []string
func main() {
// Step 1 - Read input from command line
filename = flag.String("file", "speech.txt", "Which .txt file shall I present?")
flag.Parse()
// Step 2 - Read from file
paragraphList = readText(filename)
// Step 3 - Start the GUI
go func() {
// create new window
w := app.NewWindow(
app.Title("Teleprompter"),
app.Size(unit.Dp(650), unit.Dp(600)),
)
// draw on screen
if err := draw(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func readText(filename *string) []string {
f, err := os.ReadFile(*filename)
text := []string{}
if err != nil {
log.Fatal("Error when reading file:\n ", err)
}
if err == nil {
// Convert whole text into a slice of strings.
text = strings.Split(string(f), "\n")
// Add extra empty lines a the end. Simple trick to ensure
// the last line of the speech scrolls out of the screen
for i := 1; i <= 10; i++ {
text = append(text, "")
}
}
// Alternative to reading from file, we can generate paragraphs programatically
// Handy for debugging
//for i := 1; i <= 2500; i++ {
// text = append(text, fmt.Sprintf("Eloquent speech, interesting phrase %d", i))
//}
return text
}
// The main draw function
func draw(w *app.Window) error {
// y-position for text
var scrollY unit.Dp = 0
// y-position for red focusBar
var focusBarY unit.Dp = 170
// width of text area
var textWidth unit.Dp = 550
// fontSize
var fontSize unit.Sp = 35
// Are we auto scrolling?
var autoscroll bool = false
var autospeed unit.Dp = 1
// th defines the material design style
th := material.NewTheme()
// ops are the operations from the UI
var ops op.Ops
tag := new(int)
// Listen for events from the window.
for {
switch winE := w.NextEvent().(type) {
// Should we draw a new frame?
case app.FrameEvent:
// ---------- Handle input ----------
// Time to deal with inputs since last frame.
// Since we use one global eventArea, with Tag: 0
// we here call gtx.Events(0) to get these events.
gtx := app.NewContext(&ops, winE)
for {
// To set how large each change is
var stepSize unit.Dp = 1
gtxE, ok := gtx.Event(
// TODO: pointer.Scroll and key + - not working.
pointer.Filter{Target: tag, Kinds: pointer.Scroll},
key.Filter{Name: "+"},
key.Filter{Name: "-"},
key.Filter{Name: key.NameSpace},
key.Filter{Name: "F", Optional: key.ModShift},
key.Filter{Name: "S", Optional: key.ModShift},
key.Filter{Name: "U", Optional: key.ModShift},
key.Filter{Name: "D", Optional: key.ModShift},
key.Filter{Name: "J", Optional: key.ModShift},
key.Filter{Name: "K", Optional: key.ModShift},
key.Filter{Name: "W", Optional: key.ModShift},
key.Filter{Name: "N", Optional: key.ModShift},
// (Shift)-F|(Shift)-S|(Shift)-U|(Shift)-D|(Shift)-J|(Shift)-K|(Shift)-W|(Shift)-N|Space
)
if !ok {
break
}
println(gtxE)
switch gtxE := gtxE.(type) {
// A mouse event?
case pointer.Event:
// Are we scrolling?
println("scrolling?", gtxE.Kind)
if gtxE.Kind == pointer.Scroll {
if gtxE.Modifiers == key.ModShift {
stepSize = 3
}
// Increment scrollY with gtxE.Scroll.Y
scrollY = scrollY + unit.Dp(gtxE.Scroll.Y)*stepSize
if scrollY < 0 {
scrollY = 0
}
}
// Any key
case key.EditEvent:
// To increase the fontsize
if gtxE.Text == "+" {
fontSize = fontSize + unit.Sp(stepSize)
}
// To decrease the fontsize
if gtxE.Text == "-" {
fontSize = fontSize - unit.Sp(stepSize)
}
// Only specified keys, defined in key.InputOp below
case key.Event:
// For better control, we only care about pressing the key down, not releasing it up
if gtxE.State == key.Press {
// Inrease the stepSize when pressing Shift
if gtxE.Modifiers == key.ModShift {
stepSize = 5
}
// Start/Stop
if gtxE.Name == "Space" {
autoscroll = !autoscroll
if autospeed == 0 {
autoscroll = true
autospeed = 1
}
}
if gtxE.Name == "+" {
fontSize = fontSize + unit.Sp(stepSize)
}
if gtxE.Name == "-" {
fontSize = fontSize - unit.Sp(stepSize)
}
// Scroll up
if gtxE.Name == "K" {
scrollY = scrollY - stepSize*4
if scrollY < 0 {
scrollY = 0
}
}
// Scroll down
if gtxE.Name == "J" {
scrollY = scrollY + stepSize*4
}
// Faster scrollspeed
if gtxE.Name == "F" {
autoscroll = true
autospeed += stepSize
}
// Slower scrollspeed
if gtxE.Name == "S" {
if autospeed > 0 {
autospeed -= stepSize
}
if autospeed == 0 {
autoscroll = false
}
}
// Wider text to be displayed
if gtxE.Name == "W" {
textWidth = textWidth + stepSize*10
}
// Narrow text to be displayed
if gtxE.Name == "N" {
textWidth = textWidth - stepSize*10
}
// Move the focusBar Up
if gtxE.Name == "U" {
focusBarY = focusBarY - stepSize
}
// Move the focusBar Down
if gtxE.Name == "D" {
focusBarY = focusBarY + stepSize
}
}
}
}
// ---------- LAYOUT ----------
// First we layout the user interface.
// Afterwards we add an eventArea.
// Let's start with a background color
paint.Fill(&ops, color.NRGBA{R: 0xff, G: 0xfe, B: 0xe0, A: 0xff})
// ---------- THE SCROLLING TEXT ----------
// First, check if we should autoscroll
// That's done by increasing the value of scrollY
if autoscroll {
if autospeed < 0 {
autospeed = 0
}
scrollY = scrollY + autospeed
// op.InvalidateOp{At: gtx.Now.Add(time.Second * 2 / 100)}.Add(&ops)
gtx.Execute(op.InvalidateCmd{At: gtx.Now.Add(time.Second * 2 / 100)})
}
// Then we use scrollY to control the distance from the top of the screen to the first element.
// We visualize the text using a list where each paragraph is a separate item.
var visList = layout.List{
Axis: layout.Vertical,
Position: layout.Position{
Offset: int(scrollY),
},
}
// ---------- MARGINS ----------
// Margins
var marginWidth unit.Dp
marginWidth = (unit.Dp(gtx.Constraints.Max.X) - textWidth) / 3
margins := layout.Inset{
Left: marginWidth,
Right: marginWidth,
Top: unit.Dp(0),
Bottom: unit.Dp(0),
}
// ---------- LIST WITHIN MARGINS ----------
// 1) First the margins ...
margins.Layout(gtx,
func(gtx C) D {
// 2) ... then the list inside those margins ...
return visList.Layout(gtx, len(paragraphList),
// 3) ... where each paragraph is a separate item
func(gtx C, index int) D {
// One label per paragraph
paragraph := material.Label(th, unit.Sp(float32(fontSize)), paragraphList[index])
// The text is centered
paragraph.Alignment = text.Middle
// Return the laid out paragraph
return paragraph.Layout(gtx)
},
)
},
)
// ---------- THE FOCUS BAR ----------
// Draw the transparent red focus bar.
focusBar := clip.Rect{
Min: image.Pt(0, int(focusBarY)),
Max: image.Pt(gtx.Constraints.Max.X, int(focusBarY)+int(unit.Dp(50))),
}.Push(&ops)
paint.ColorOp{Color: color.NRGBA{R: 0xff, A: 0x66}}.Add(&ops)
paint.PaintOp{}.Add(&ops)
focusBar.Pop()
// // ---------- COLLECT INPUT ----------
// // Create an eventArea to collect events. It has the same size as the full window.
// // First we Push() it on the stack, then add code to catch keys and pointers
// // Finally we Pop() it
// eventArea := clip.Rect(
// image.Rectangle{
// // From top left
// Min: image.Point{0, 0},
// // To bottom right
// Max: image.Point{gtx.Constraints.Max.X, gtx.Constraints.Max.Y},
// },
// ).Push(gtx.Ops)
// // Since Gio is stateless we must Tag events, to make sure we know where they came from.
// // Such a tag can anything really, so we simply use Tag: 0.
// // Later we retireve these events with gtx.Events(0)
// // 1) We first add a pointer.InputOp to catch scrolling:
// pointer.InputOp{
// Types: pointer.Scroll,
// Tag: 0, // Use Tag: 0 as the event routing tag, and retireve it through gtx.Events(0)
// // ScrollBounds sets bounds on scrolling, and we want it to be non-zero.
// // In practice it seldom reached 100, so [MinInt8,MaxInt8] or [-128,127] should be enough
// ScrollBounds: image.Rectangle{
// Min: image.Point{
// X: 0,
// Y: math.MinInt8, //-128
// },
// Max: image.Point{
// X: 0,
// Y: math.MaxInt8, //+127
// },
// },
// }.Add(gtx.Ops)
// // 2) Next we add key.FocusOp,
// // Needed for general keybaord input, except the ones defined explicitly in key.InputOp
// // These inputs are retrieved as key.EditEvent
// // key.FocusOp{
// // Tag: 0, // Use Tag: 0 as the event routing tag, and retireve it through gtx.Events(0)
// // }.Add(gtx.Ops)
// gtx.Execute(key.FocusCmd{
// Tag: tag,
// })
// // 3) Finally we add key.InputOp to catch specific keys
// // (Shift) means an optional Shift
// // These inputs are retrieved as key.Event
// key.InputOp{
// Keys: key.Set("(Shift)-F|(Shift)-S|(Shift)-U|(Shift)-D|(Shift)-J|(Shift)-K|(Shift)-W|(Shift)-N|Space"),
// Tag: 0, // Use Tag: 0 as the event routing tag, and retireve it through gtx.Events(0)
// }.Add(gtx.Ops)
// // Finally Pop() the eventArea from the stack
// eventArea.Pop()
// ---------- FINALIZE ----------
// Frame completes the FrameEvent by drawing the graphical operations from ops into the window.
winE.Frame(&ops)
// Should we shut down?
case app.DestroyEvent:
return winE.Err
}
}
return nil
}
gio version bump up#
When I was following Jon Strand’s tutorials, I tried to run the code with latest gio verion, but sometimes I met some problems, so I collected them and hope they are useful for all of us.