github.com/df-mc/dragonfly@v0.9.13/server/player/form/menu.go (about)

     1  package form
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  )
     8  
     9  // Menu represents a menu form. These menus are made up of a title and a body, with a number of buttons which
    10  // come below the body. These buttons may also have images on the side of them.
    11  type Menu struct {
    12  	title, body string
    13  	submittable MenuSubmittable
    14  	buttons     []Button
    15  }
    16  
    17  // NewMenu creates a new Menu form using the MenuSubmittable passed to handle the output of the form. The
    18  // title passed is formatted following the rules of fmt.Sprintln.
    19  func NewMenu(submittable MenuSubmittable, title ...any) Menu {
    20  	t := reflect.TypeOf(submittable)
    21  	if t.Kind() != reflect.Struct {
    22  		panic("submittable must be struct")
    23  	}
    24  	m := Menu{title: format(title), submittable: submittable}
    25  	m.verify()
    26  	return m
    27  }
    28  
    29  // MarshalJSON ...
    30  func (m Menu) MarshalJSON() ([]byte, error) {
    31  	return json.Marshal(map[string]any{
    32  		"type":    "form",
    33  		"title":   m.title,
    34  		"content": m.body,
    35  		"buttons": m.Buttons(),
    36  	})
    37  }
    38  
    39  // WithBody creates a copy of the Menu form and changes its body to the body passed, after which the new Menu
    40  // form is returned. The text is formatted following the rules of fmt.Sprintln.
    41  func (m Menu) WithBody(body ...any) Menu {
    42  	m.body = format(body)
    43  	return m
    44  }
    45  
    46  // WithButtons creates a copy of the Menu form and appends the buttons passed to the existing buttons, after
    47  // which the new Menu form is returned.
    48  func (m Menu) WithButtons(buttons ...Button) Menu {
    49  	m.buttons = append(m.buttons, buttons...)
    50  	return m
    51  }
    52  
    53  // Title returns the formatted title passed to the menu upon construction using NewMenu().
    54  func (m Menu) Title() string {
    55  	return m.title
    56  }
    57  
    58  // Body returns the formatted text in the body passed to the menu using WithBody().
    59  func (m Menu) Body() string {
    60  	return m.body
    61  }
    62  
    63  // Buttons returns a list of all buttons of the MenuSubmittable. It parses them from the fields using
    64  // reflection and returns them.
    65  func (m Menu) Buttons() []Button {
    66  	v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
    67  	v.Set(reflect.ValueOf(m.submittable))
    68  
    69  	buttons := make([]Button, 0)
    70  	for i := 0; i < v.NumField(); i++ {
    71  		field := v.Field(i)
    72  		if !field.CanSet() {
    73  			continue
    74  		}
    75  		// Each exported field is guaranteed to be of type Button.
    76  		buttons = append(buttons, field.Interface().(Button))
    77  	}
    78  	buttons = append(buttons, m.buttons...)
    79  	return buttons
    80  }
    81  
    82  // SubmitJSON submits a JSON value to the menu, containing the index of the button clicked.
    83  func (m Menu) SubmitJSON(b []byte, submitter Submitter) error {
    84  	if b == nil {
    85  		if closer, ok := m.submittable.(Closer); ok {
    86  			closer.Close(submitter)
    87  		}
    88  		return nil
    89  	}
    90  
    91  	var index uint
    92  	err := json.Unmarshal(b, &index)
    93  	if err != nil {
    94  		return fmt.Errorf("cannot parse button index as int: %w", err)
    95  	}
    96  	buttons := m.Buttons()
    97  	if index >= uint(len(buttons)) {
    98  		return fmt.Errorf("button index points to inexistent button: %v (only %v buttons present)", index, len(buttons))
    99  	}
   100  	m.submittable.Submit(submitter, buttons[index])
   101  	return nil
   102  }
   103  
   104  // verify verifies if the form is valid, checking all fields are of the type Button. It panics if the form is
   105  // not valid.
   106  func (m Menu) verify() {
   107  	v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
   108  	v.Set(reflect.ValueOf(m.submittable))
   109  	for i := 0; i < v.NumField(); i++ {
   110  		if !v.Field(i).CanSet() {
   111  			continue
   112  		}
   113  		if _, ok := v.Field(i).Interface().(Button); !ok {
   114  			panic("all exported fields must be of the type form.Button")
   115  		}
   116  	}
   117  }