gemini-browser

A text-based gemini browser
git clone git://git.laack.co/gemini-browser.git
Log | Files | Refs | README

modal.go (6471B)


      1 package tview
      2 
      3 import (
      4 	"github.com/gdamore/tcell/v2"
      5 )
      6 
      7 // Modal is a centered message window used to inform the user or prompt them
      8 // for an immediate decision. It needs to have at least one button (added via
      9 // [Modal.AddButtons]) or it will never disappear.
     10 //
     11 // See https://github.com/rivo/tview/wiki/Modal for an example.
     12 type Modal struct {
     13 	*Box
     14 
     15 	// The frame embedded in the modal.
     16 	frame *Frame
     17 
     18 	// The form embedded in the modal's frame.
     19 	form *Form
     20 
     21 	// The message text (original, not word-wrapped).
     22 	text string
     23 
     24 	// The text color.
     25 	textColor tcell.Color
     26 
     27 	// The optional callback for when the user clicked one of the buttons. It
     28 	// receives the index of the clicked button and the button's label.
     29 	done func(buttonIndex int, buttonLabel string)
     30 }
     31 
     32 // NewModal returns a new modal message window.
     33 func NewModal() *Modal {
     34 	m := &Modal{
     35 		Box:       NewBox().SetBorder(true).SetBackgroundColor(Styles.ContrastBackgroundColor),
     36 		textColor: Styles.PrimaryTextColor,
     37 	}
     38 	m.form = NewForm().
     39 		SetButtonsAlign(AlignCenter).
     40 		SetButtonBackgroundColor(Styles.PrimitiveBackgroundColor).
     41 		SetButtonTextColor(Styles.PrimaryTextColor)
     42 	m.form.SetBackgroundColor(Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0)
     43 	m.form.SetCancelFunc(func() {
     44 		if m.done != nil {
     45 			m.done(-1, "")
     46 		}
     47 	})
     48 	m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
     49 	m.frame.SetBackgroundColor(Styles.ContrastBackgroundColor).
     50 		SetBorderPadding(1, 1, 1, 1)
     51 	return m
     52 }
     53 
     54 // SetBackgroundColor sets the color of the modal frame background.
     55 func (m *Modal) SetBackgroundColor(color tcell.Color) *Modal {
     56 	m.form.SetBackgroundColor(color)
     57 	m.frame.SetBackgroundColor(color)
     58 	return m
     59 }
     60 
     61 // SetTextColor sets the color of the message text.
     62 func (m *Modal) SetTextColor(color tcell.Color) *Modal {
     63 	m.textColor = color
     64 	return m
     65 }
     66 
     67 // SetButtonBackgroundColor sets the background color of the buttons.
     68 func (m *Modal) SetButtonBackgroundColor(color tcell.Color) *Modal {
     69 	m.form.SetButtonBackgroundColor(color)
     70 	return m
     71 }
     72 
     73 // SetButtonTextColor sets the color of the button texts.
     74 func (m *Modal) SetButtonTextColor(color tcell.Color) *Modal {
     75 	m.form.SetButtonTextColor(color)
     76 	return m
     77 }
     78 
     79 // SetButtonStyle sets the style of the buttons when they are not focused.
     80 func (m *Modal) SetButtonStyle(style tcell.Style) *Modal {
     81 	m.form.SetButtonStyle(style)
     82 	return m
     83 }
     84 
     85 // SetButtonActivatedStyle sets the style of the buttons when they are focused.
     86 func (m *Modal) SetButtonActivatedStyle(style tcell.Style) *Modal {
     87 	m.form.SetButtonActivatedStyle(style)
     88 	return m
     89 }
     90 
     91 // SetDoneFunc sets a handler which is called when one of the buttons was
     92 // pressed. It receives the index of the button as well as its label text. The
     93 // handler is also called when the user presses the Escape key. The index will
     94 // then be negative and the label text an empty string.
     95 func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {
     96 	m.done = handler
     97 	return m
     98 }
     99 
    100 // SetText sets the message text of the window. The text may contain line
    101 // breaks but style tag states will not transfer to following lines. Note that
    102 // words are wrapped, too, based on the final size of the window.
    103 func (m *Modal) SetText(text string) *Modal {
    104 	m.text = text
    105 	return m
    106 }
    107 
    108 // AddButtons adds buttons to the window. There must be at least one button and
    109 // a "done" handler so the window can be closed again.
    110 func (m *Modal) AddButtons(labels []string) *Modal {
    111 	for index, label := range labels {
    112 		func(i int, l string) {
    113 			m.form.AddButton(label, func() {
    114 				if m.done != nil {
    115 					m.done(i, l)
    116 				}
    117 			})
    118 			button := m.form.GetButton(m.form.GetButtonCount() - 1)
    119 			button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
    120 				switch event.Key() {
    121 				case tcell.KeyDown, tcell.KeyRight:
    122 					return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
    123 				case tcell.KeyUp, tcell.KeyLeft:
    124 					return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
    125 				}
    126 				return event
    127 			})
    128 		}(index, label)
    129 	}
    130 	return m
    131 }
    132 
    133 // ClearButtons removes all buttons from the window.
    134 func (m *Modal) ClearButtons() *Modal {
    135 	m.form.ClearButtons()
    136 	return m
    137 }
    138 
    139 // SetFocus shifts the focus to the button with the given index.
    140 func (m *Modal) SetFocus(index int) *Modal {
    141 	m.form.SetFocus(index)
    142 	return m
    143 }
    144 
    145 // Focus is called when this primitive receives focus.
    146 func (m *Modal) Focus(delegate func(p Primitive)) {
    147 	delegate(m.form)
    148 }
    149 
    150 // HasFocus returns whether or not this primitive has focus.
    151 func (m *Modal) HasFocus() bool {
    152 	return m.form.HasFocus()
    153 }
    154 
    155 // Draw draws this primitive onto the screen.
    156 func (m *Modal) Draw(screen tcell.Screen) {
    157 	// Calculate the width of this modal.
    158 	buttonsWidth := 0
    159 	for _, button := range m.form.buttons {
    160 		buttonsWidth += TaggedStringWidth(button.text) + 4 + 2
    161 	}
    162 	buttonsWidth -= 2
    163 	screenWidth, screenHeight := screen.Size()
    164 	width := screenWidth / 3
    165 	if width < buttonsWidth {
    166 		width = buttonsWidth
    167 	}
    168 	// width is now without the box border.
    169 
    170 	// Reset the text and find out how wide it is.
    171 	m.frame.Clear()
    172 	lines := WordWrap(m.text, width)
    173 	for _, line := range lines {
    174 		m.frame.AddText(line, true, AlignCenter, m.textColor)
    175 	}
    176 
    177 	// Set the modal's position and size.
    178 	height := len(lines) + 6
    179 	width += 4
    180 	x := (screenWidth - width) / 2
    181 	y := (screenHeight - height) / 2
    182 	m.SetRect(x, y, width, height)
    183 
    184 	// Draw the frame.
    185 	m.Box.DrawForSubclass(screen, m)
    186 	x, y, width, height = m.GetInnerRect()
    187 	m.frame.SetRect(x, y, width, height)
    188 	m.frame.Draw(screen)
    189 }
    190 
    191 // MouseHandler returns the mouse handler for this primitive.
    192 func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
    193 	return m.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
    194 		// Pass mouse events on to the form.
    195 		consumed, capture = m.form.MouseHandler()(action, event, setFocus)
    196 		if !consumed && action == MouseLeftDown && m.InRect(event.Position()) {
    197 			setFocus(m)
    198 			consumed = true
    199 		}
    200 		return
    201 	})
    202 }
    203 
    204 // InputHandler returns the handler for this primitive.
    205 func (m *Modal) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
    206 	return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
    207 		if m.frame.HasFocus() {
    208 			if handler := m.frame.InputHandler(); handler != nil {
    209 				handler(event, setFocus)
    210 				return
    211 			}
    212 		}
    213 	})
    214 }