...

Source file src/code.rocketnine.space/tslocum/cview/modal.go

Documentation: code.rocketnine.space/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/gdamore/tcell/v2"
     7  )
     8  
     9  // Modal is a centered message window used to inform the user or prompt them
    10  // for an immediate decision. It needs to have at least one button (added via
    11  // AddButtons) or it will never disappear. You may change the title and
    12  // appearance of the window by modifying the Frame returned by GetFrame. You
    13  // may include additional elements within the window by modifying the Form
    14  // returned by GetForm.
    15  type Modal struct {
    16  	*Box
    17  
    18  	// The Frame embedded in the Modal.
    19  	frame *Frame
    20  
    21  	// The Form embedded in the Modal's Frame.
    22  	form *Form
    23  
    24  	// The message text (original, not word-wrapped).
    25  	text string
    26  
    27  	// The text color.
    28  	textColor tcell.Color
    29  
    30  	// The text alignment.
    31  	textAlign int
    32  
    33  	// The optional callback for when the user clicked one of the buttons. It
    34  	// receives the index of the clicked button and the button's label.
    35  	done func(buttonIndex int, buttonLabel string)
    36  
    37  	sync.RWMutex
    38  }
    39  
    40  // NewModal returns a new centered message window.
    41  func NewModal() *Modal {
    42  	m := &Modal{
    43  		Box:       NewBox(),
    44  		textColor: Styles.PrimaryTextColor,
    45  		textAlign: AlignCenter,
    46  	}
    47  
    48  	m.form = NewForm()
    49  	m.form.SetButtonsAlign(AlignCenter)
    50  	m.form.SetPadding(0, 0, 0, 0)
    51  	m.form.SetCancelFunc(func() {
    52  		if m.done != nil {
    53  			m.done(-1, "")
    54  		}
    55  	})
    56  
    57  	m.frame = NewFrame(m.form)
    58  	m.frame.SetBorder(true)
    59  	m.frame.SetBorders(0, 0, 1, 0, 0, 0)
    60  	m.frame.SetPadding(1, 1, 1, 1)
    61  
    62  	m.focus = m
    63  	return m
    64  }
    65  
    66  // SetBackgroundColor sets the color of the Modal Frame background.
    67  func (m *Modal) SetBackgroundColor(color tcell.Color) {
    68  	m.Lock()
    69  	defer m.Unlock()
    70  
    71  	m.form.SetBackgroundColor(color)
    72  	m.frame.SetBackgroundColor(color)
    73  }
    74  
    75  // SetTextColor sets the color of the message text.
    76  func (m *Modal) SetTextColor(color tcell.Color) {
    77  	m.Lock()
    78  	defer m.Unlock()
    79  
    80  	m.textColor = color
    81  }
    82  
    83  // SetButtonBackgroundColor sets the background color of the buttons.
    84  func (m *Modal) SetButtonBackgroundColor(color tcell.Color) {
    85  	m.Lock()
    86  	defer m.Unlock()
    87  
    88  	m.form.SetButtonBackgroundColor(color)
    89  }
    90  
    91  // SetButtonTextColor sets the color of the button texts.
    92  func (m *Modal) SetButtonTextColor(color tcell.Color) {
    93  	m.Lock()
    94  	defer m.Unlock()
    95  
    96  	m.form.SetButtonTextColor(color)
    97  }
    98  
    99  // SetButtonsAlign sets the horizontal alignment of the buttons. This must be
   100  // either AlignLeft, AlignCenter (the default), or AlignRight.
   101  func (m *Modal) SetButtonsAlign(align int) {
   102  	m.Lock()
   103  	defer m.Unlock()
   104  
   105  	m.form.SetButtonsAlign(align)
   106  }
   107  
   108  // SetDoneFunc sets a handler which is called when one of the buttons was
   109  // pressed. It receives the index of the button as well as its label text. The
   110  // handler is also called when the user presses the Escape key. The index will
   111  // then be negative and the label text an empty string.
   112  func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) {
   113  	m.Lock()
   114  	defer m.Unlock()
   115  
   116  	m.done = handler
   117  }
   118  
   119  // SetText sets the message text of the window. The text may contain line
   120  // breaks. Note that words are wrapped, too, based on the final size of the
   121  // window.
   122  func (m *Modal) SetText(text string) {
   123  	m.Lock()
   124  	defer m.Unlock()
   125  
   126  	m.text = text
   127  }
   128  
   129  // SetTextAlign sets the horizontal alignment of the text. This must be either
   130  // AlignLeft, AlignCenter (the default), or AlignRight.
   131  func (m *Modal) SetTextAlign(align int) {
   132  	m.Lock()
   133  	defer m.Unlock()
   134  
   135  	m.textAlign = align
   136  }
   137  
   138  // GetForm returns the Form embedded in the window. The returned Form may be
   139  // modified to include additional elements (e.g. AddInputField, AddFormItem).
   140  func (m *Modal) GetForm() *Form {
   141  	m.RLock()
   142  	defer m.RUnlock()
   143  
   144  	return m.form
   145  }
   146  
   147  // GetFrame returns the Frame embedded in the window.
   148  func (m *Modal) GetFrame() *Frame {
   149  	m.RLock()
   150  	defer m.RUnlock()
   151  
   152  	return m.frame
   153  }
   154  
   155  // AddButtons adds buttons to the window. There must be at least one button and
   156  // a "done" handler so the window can be closed again.
   157  func (m *Modal) AddButtons(labels []string) {
   158  	m.Lock()
   159  	defer m.Unlock()
   160  
   161  	for index, label := range labels {
   162  		func(i int, l string) {
   163  			m.form.AddButton(label, func() {
   164  				if m.done != nil {
   165  					m.done(i, l)
   166  				}
   167  			})
   168  			button := m.form.GetButton(m.form.GetButtonCount() - 1)
   169  			button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
   170  				switch event.Key() {
   171  				case tcell.KeyDown, tcell.KeyRight:
   172  					return tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
   173  				case tcell.KeyUp, tcell.KeyLeft:
   174  					return tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
   175  				}
   176  				return event
   177  			})
   178  		}(index, label)
   179  	}
   180  }
   181  
   182  // ClearButtons removes all buttons from the window.
   183  func (m *Modal) ClearButtons() {
   184  	m.Lock()
   185  	defer m.Unlock()
   186  
   187  	m.form.ClearButtons()
   188  }
   189  
   190  // SetFocus shifts the focus to the button with the given index.
   191  func (m *Modal) SetFocus(index int) {
   192  	m.Lock()
   193  	defer m.Unlock()
   194  
   195  	m.form.SetFocus(index)
   196  }
   197  
   198  // Focus is called when this primitive receives focus.
   199  func (m *Modal) Focus(delegate func(p Primitive)) {
   200  	delegate(m.form)
   201  }
   202  
   203  // HasFocus returns whether or not this primitive has focus.
   204  func (m *Modal) HasFocus() bool {
   205  	return m.GetForm().HasFocus()
   206  }
   207  
   208  // Draw draws this primitive onto the screen.
   209  func (m *Modal) Draw(screen tcell.Screen) {
   210  	if !m.GetVisible() {
   211  		return
   212  	}
   213  
   214  	formItemCount := m.form.GetFormItemCount()
   215  
   216  	m.Lock()
   217  	defer m.Unlock()
   218  
   219  	// Calculate the width of this Modal.
   220  	buttonsWidth := 0
   221  	for _, button := range m.form.buttons {
   222  		buttonsWidth += TaggedTextWidth(button.label) + 4 + 2
   223  	}
   224  	buttonsWidth -= 2
   225  	screenWidth, screenHeight := screen.Size()
   226  	width := screenWidth / 3
   227  	if width < buttonsWidth {
   228  		width = buttonsWidth
   229  	}
   230  	// width is now without the box border.
   231  
   232  	// Reset the text and find out how wide it is.
   233  	m.frame.Clear()
   234  	lines := WordWrap(m.text, width)
   235  	for _, line := range lines {
   236  		m.frame.AddText(line, true, m.textAlign, m.textColor)
   237  	}
   238  
   239  	// Set the Modal's position and size.
   240  	height := len(lines) + (formItemCount * 2) + 6
   241  	width += 4
   242  	x := (screenWidth - width) / 2
   243  	y := (screenHeight - height) / 2
   244  	m.SetRect(x, y, width, height)
   245  
   246  	// Draw the frame.
   247  	m.frame.SetRect(x, y, width, height)
   248  	m.frame.Draw(screen)
   249  }
   250  
   251  // MouseHandler returns the mouse handler for this primitive.
   252  func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   253  	return m.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
   254  		// Pass mouse events on to the form.
   255  		consumed, capture = m.form.MouseHandler()(action, event, setFocus)
   256  		if !consumed && action == MouseLeftClick && m.InRect(event.Position()) {
   257  			setFocus(m)
   258  			consumed = true
   259  		}
   260  		return
   261  	})
   262  }
   263  

View as plain text