~gioverse/chat

ref: 84203ce5a460 chat/example/kitchen/room.go -rw-r--r-- 4.1 KiB
84203ce5Jack Mordaunt widget/material: maintain size for empty images 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package main

import (
	"math/rand"
	"sync"

	"gioui.org/widget"
	"git.sr.ht/~gioverse/chat/example/kitchen/appwidget"
	"git.sr.ht/~gioverse/chat/example/kitchen/model"
	"git.sr.ht/~gioverse/chat/list"
)

// Rooms contains a selectable list of rooms.
type Rooms struct {
	active  int
	changed bool
	List    []Room
	sync.Mutex
}

// Room is a unique conversation context.
// Note(jfm): Allocates model and interact, not sure about that.
// Avoids the UI needing to allocate two lists (interact/model) for the
// rooms.
type Room struct {
	// Room model defines the backend data describing a room.
	*model.Room
	// Interact defines the interactive state for a room widget.
	Interact appwidget.Room
	// Messages implements what would be a backend data model.
	// This would be the facade to your business api.
	// This is the source of truth.
	// This type gets asked to create messages and queried for message history.
	Messages *RowTracker
	// ListState dynamically manages list state.
	// This lets us surf across a vast ocean of infinite messages, only ever
	// rendering what is actualy viewable.
	// The widget.List consumes this during layout.
	ListState *list.Manager
	// List implements the raw scrolling, adding scrollbars and responding
	// to mousewheel / touch fling gestures.
	List widget.List
	// Editor contains the edit buffer for composing messages.
	Editor widget.Editor
	sync.Mutex
}

// SetComposing sets the composing status for a user in this room.
// Note: doesn't actually verify the user pertains to this room.
func (r *Room) SetComposing(user string, isComposing bool) {
	r.Room.SetComposing(user, isComposing)
}

// Send attempts to send arbitrary content as a message from the specified user.
func (r *Room) Send(user, content string) {
	row := r.Messages.Send(user, content)
	r.Lock()
	r.Room.Latest = &row
	r.Unlock()
	go r.ListState.Modify([]list.Element{row}, nil, nil)
}

// SendLocal attempts to send the contents of the edit buffer as a
// to the model.
// All of the work of this method is dispatched in a new goroutine
// so that it can safely be called from layout code without blocking.
func (r *Room) SendLocal(msg string) {
	go func() {
		r.Lock()
		row := r.Messages.Send(r.Messages.Local.Name, msg)
		r.Room.Latest = &row
		r.Unlock()
		r.ListState.Modify([]list.Element{row}, nil, nil)
	}()
}

// NewRow generates a new row in the Room's RowTracker and inserts it
// into the list manager for the room.
func (r *Room) NewRow() {
	row := r.Messages.NewRow()
	go r.ListState.Modify([]list.Element{row}, nil, nil)
}

// DeleteRow removes the row with the provided serial from both the
// row tracker and the list manager for the room.
func (r *Room) DeleteRow(serial list.Serial) {
	r.Messages.Delete(serial)
	go r.ListState.Modify(nil, nil, []list.Serial{serial})
}

// Active returns the active room, empty if not rooms are available.
func (r *Rooms) Active() *Room {
	r.Lock()
	defer r.Unlock()
	if len(r.List) == 0 {
		return &Room{}
	}
	return &r.List[r.active]
}

// Latest returns a copy of the latest message for the room.
func (r *Room) Latest() model.Message {
	r.Lock()
	defer r.Unlock()
	if r.Room.Latest == nil {
		return model.Message{}
	}
	return *r.Room.Latest
}

// Select the room at the given index.
// Index is bounded by [0, len(rooms)).
func (r *Rooms) Select(index int) {
	r.Lock()
	defer r.Unlock()
	if index < 0 {
		index = 0
	}
	if index > len(r.List) {
		index = len(r.List) - 1
	}
	r.changed = true
	r.List[r.active].Interact.Active = false
	r.active = index
	r.List[r.active].Interact.Active = true
}

// Changed if the active room has changed since last call.
func (r *Rooms) Changed() bool {
	r.Lock()
	defer r.Unlock()
	defer func() { r.changed = false }()
	return r.changed
}

// Index returns a pointer to a Room at the given index.
// Index is bounded by [0, len(rooms)).
func (r *Rooms) Index(index int) *Room {
	r.Lock()
	defer r.Unlock()
	if index < 0 {
		index = 0
	}
	if index > len(r.List) {
		index = len(r.List) - 1
	}
	return &r.List[index]
}

// Index returns a pointer to a random Room in the list.
func (r *Rooms) Random() *Room {
	r.Lock()
	defer r.Unlock()
	return &r.List[rand.Intn(len(r.List)-1)]
}