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 }