Skip to content

Instantly share code, notes, and snippets.

@mingrammer
Last active March 26, 2025 02:49
Show Gist options
  • Save mingrammer/4094fc32da70fc69a8b84ec69bf8da33 to your computer and use it in GitHub Desktop.
Save mingrammer/4094fc32da70fc69a8b84ec69bf8da33 to your computer and use it in GitHub Desktop.
Button click handler
package main
import (
"fmt"
"time"
)
type eventType int
const (
downUp eventType = iota
upDown
)
func (et eventType) String() string {
switch et {
case downUp:
return "Down Up"
case upDown:
return "Up Down"
default:
return "Unknown"
}
}
type clickType int
const (
singleClick clickType = iota
doubleClick
)
func (ct clickType) String() string {
switch ct {
case singleClick:
return "Single Click"
case doubleClick:
return "Double Click"
default:
return "Unknown"
}
}
const doubleClickMs = 500
type input struct {
buttonID int
tsMs int
eventTyp eventType
value string
}
type queue []input
type output struct {
clickTyp clickType
tsMs int
}
type buttonState struct {
lastClickTime int
clickCount int
timer *time.Timer
}
func handleEvent(
event input, buttonStates map[int]*buttonState, results map[int][]output,
) {
state, ok := buttonStates[event.buttonID]
if !ok {
state = &buttonState{}
buttonStates[event.buttonID] = state
}
// 이벤트 타입이 downUp 경우 무시 (클릭 완성의 판정은 upDown으로 함)
// 이 케이스는 더블 클릭을 판정하는 중간에 다른 버튼이 눌렸을 때, 기존 버튼의 상태를 초기화하기 위함.
if event.eventTyp == downUp {
// 다른 버튼이 눌렸을 때, 기존 버튼 상태 초기화
for key, btnState := range buttonStates {
if key != event.buttonID && btnState.clickCount == 1 && btnState.timer != nil {
btnState.timer.Stop() // 타이머 종료
btnState.clickCount = 0
results[key] = append(results[key],
output{
clickTyp: singleClick,
tsMs: btnState.lastClickTime,
})
}
}
}
// upDown 이벤트 처리 (클릭 판정)
if event.eventTyp == upDown {
// 다른 버튼 클릭 판정시, 기존 버튼 상태 초기화
for key, btnState := range buttonStates {
if key != event.buttonID && btnState.clickCount == 1 && btnState.timer != nil {
btnState.timer.Stop() // 타이머 종료
btnState.clickCount = 0
results[key] = append(results[key],
output{
clickTyp: singleClick,
tsMs: btnState.lastClickTime,
})
}
}
// 첫 번째 클릭인지 확인
if state.clickCount == 0 {
state.lastClickTime = event.tsMs
state.clickCount = 1
// Double Click 대기 타이머 시작
state.timer = time.AfterFunc(doubleClickMs*time.Millisecond, func() {
// doubleClickMs 이내에 두 번째 클릭이 없을 경우 Single Click으로 확정
if state.clickCount == 1 {
results[event.buttonID] = append(results[event.buttonID],
output{
clickTyp: singleClick,
tsMs: state.lastClickTime,
})
}
state.clickCount = 0
})
} else if state.clickCount == 1 {
// 두 번째 downUp 이벤트시 시간 차이를 확인하여 Double Click 판정
elapsedTime := event.tsMs - state.lastClickTime
if elapsedTime <= doubleClickMs {
// Double Click 발생
if state.timer != nil {
state.timer.Stop() // 타이머 종료
}
results[event.buttonID] = append(
results[event.buttonID], output{
clickTyp: doubleClick,
tsMs: event.tsMs,
})
} else {
// 시간 차이가 doubleClickMs 이상인 경우 Single Click으로 확정하고 새 클릭 처리
results[event.buttonID] = append(results[event.buttonID],
output{
clickTyp: singleClick,
tsMs: state.lastClickTime,
})
state.lastClickTime = event.tsMs
state.clickCount = 1
state.timer = time.AfterFunc(doubleClickMs*time.Millisecond, func() {
if state.clickCount == 1 {
results[event.buttonID] = append(results[event.buttonID],
output{
clickTyp: singleClick,
tsMs: state.lastClickTime,
})
}
state.clickCount = 0
})
return
}
state.clickCount = 0 // 상태 초기화
}
}
}
func process(events queue) map[int][]output {
results := make(map[int][]output)
// Button State 초기화
buttonStates := make(map[int]*buttonState)
for _, event := range events {
handleEvent(event, buttonStates, results)
}
return results
}
func main() {
examples := []struct {
description string
q queue
}{
{
"Single Click 1",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{2, 2000, upDown, "a"},
{2, 2100, downUp, "a"},
},
},
{
"Single Click 2",
queue{
{1, 1000, upDown, "a"},
{2, 1100, upDown, "a"},
{3, 1200, upDown, "a"},
{4, 1300, upDown, "a"},
{1, 1400, downUp, "a"},
{2, 1500, downUp, "a"},
{3, 1600, downUp, "a"},
{4, 1700, downUp, "a"},
},
},
{
"Double Click 1",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{1, 1200, upDown, "a"},
{1, 1300, downUp, "a"},
},
},
{
"Double Click 2",
queue{
{1, 1000, upDown, "a"},
{1, 1200, downUp, "a"},
{1, 1300, upDown, "a"},
{1, 2000, downUp, "a"},
},
},
{
"두 개의 빠른 클릭간 시간차가 500ms 초과인 경우",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{1, 1550, upDown, "a"},
{1, 1650, downUp, "a"},
},
},
{
"클릭간 시간차가 500ms 이하지만, 중간에 다른 버튼의 upDown 이 발생한 경우",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{2, 1200, upDown, "a"},
{1, 1400, upDown, "a"},
{1, 1500, downUp, "a"},
},
},
{
"클릭간 시간차가 500ms 이하지만, 중간에 다른 버튼의 downUp 이 발생한 경우",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{2, 1300, downUp, "a"},
{1, 1400, upDown, "a"},
{1, 1500, downUp, "a"},
},
},
{
"한 버튼의 두 번째 downUp 이벤트가 다른 버튼의 입력 이벤트 이후 발생한 경우",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{1, 1200, upDown, "a"},
{2, 1300, upDown, "a"},
{1, 1400, downUp, "a"},
},
},
{
"한 버튼의 입력 이벤트 사이에 다른 버튼의 Double Click 이 발생한 경우",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{3, 1200, upDown, "a"},
{3, 1300, downUp, "a"},
{3, 1400, upDown, "a"},
{3, 1500, downUp, "a"},
{1, 1600, upDown, "a"},
{1, 1700, downUp, "a"},
},
},
{
"빠르게 세번 클릭한 경우 (Double Click 과 Single Click 이 차례로 발생)",
queue{
{1, 1000, upDown, "a"},
{1, 1100, downUp, "a"},
{1, 1200, upDown, "a"},
{1, 1300, downUp, "a"},
{1, 1400, upDown, "a"},
{1, 1500, downUp, "a"},
},
},
}
for i, e := range examples {
fmt.Println("-----------------------------------------------")
fmt.Printf("Example %d: %s\n", i+1, e.description)
fmt.Println("-----------------------------------------------")
fmt.Println()
fmt.Println("-----------------------------------------------")
fmt.Println("Queue")
fmt.Println("-----------------------------------------------")
for _, o := range e.q {
fmt.Printf(
"- btn id: %d, ts: %d, event: %s, value: %s\n",
o.buttonID, o.tsMs, o.eventTyp, o.value,
)
}
fmt.Println("-----------------------------------------------")
fmt.Println()
fmt.Println("-----------------------------------------------")
fmt.Println("Output")
fmt.Println("-----------------------------------------------")
result := process(e.q)
for k, v := range result {
fmt.Printf("btn id: %d\n", k)
for _, o := range v {
fmt.Printf("- click type: %s, ts: %d\n", o.clickTyp, o.tsMs)
}
}
fmt.Println("-----------------------------------------------")
fmt.Println()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment