aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/macros/RadialMenu.kt
blob: 2e09c44567297b3bdace494762e003e4f2c16627 (plain)
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
package moe.nea.firmament.features.macros

import org.joml.Vector2f
import util.render.CustomRenderLayers
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import net.minecraft.client.gui.DrawContext
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HudRenderEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldKeyboardEvent
import moe.nea.firmament.events.WorldMouseMoveEvent
import moe.nea.firmament.features.macros.RadialMenuViewer.RadialMenu
import moe.nea.firmament.features.macros.RadialMenuViewer.RadialMenuOption
import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.render.RenderCircleProgress
import moe.nea.firmament.util.render.drawLine
import moe.nea.firmament.util.render.lerpAngle
import moe.nea.firmament.util.render.wrapAngle
import moe.nea.firmament.util.render.τ

object RadialMenuViewer {
	interface RadialMenu {
		val key: SavedKeyBinding
		val options: List<RadialMenuOption>
	}

	interface RadialMenuOption {
		val isEnabled: Boolean
		fun resolve()
		fun renderSlice(drawContext: DrawContext)
	}

	var activeMenu: RadialMenu? = null
		set(value) {
			field = value
			delta = Vector2f(0F, 0F)
		}
	var delta = Vector2f(0F, 0F)
	val maxSelectionSize = 100F

	@Subscribe
	fun onMouseMotion(event: WorldMouseMoveEvent) {
		val menu = activeMenu ?: return
		event.cancel()
		delta.add(event.deltaX.toFloat(), event.deltaY.toFloat())
		val m = delta.lengthSquared()
		if (m > maxSelectionSize * maxSelectionSize) {
			delta.mul(maxSelectionSize / sqrt(m))
		}
	}

	val INNER_CIRCLE_RADIUS = 16

	@Subscribe
	fun onRender(event: HudRenderEvent) {
		val menu = activeMenu ?: return
		val mat = event.context.matrices
		mat.push()
		mat.translate(
			(MC.window.scaledWidth) / 2F,
			(MC.window.scaledHeight) / 2F,
			0F
		)
		val sliceWidth = (τ / menu.options.size).toFloat()
		var selectedAngle = wrapAngle(atan2(delta.y, delta.x))
		if (delta.lengthSquared() < INNER_CIRCLE_RADIUS * INNER_CIRCLE_RADIUS)
			selectedAngle = Float.NaN
		for ((idx, option) in menu.options.withIndex()) {
			val range = (sliceWidth * idx)..(sliceWidth * (idx + 1))
			mat.push()
			mat.scale(64F, 64F, 1F)
			val cutout = INNER_CIRCLE_RADIUS / 64F / 2
			RenderCircleProgress.renderCircularSlice(
				event.context,
				CustomRenderLayers.TRANSLUCENT_CIRCLE_GUI,
				0F, 1F, 0F, 1F,
				range,
				color = if (selectedAngle in range) 0x70A0A0A0 else 0x70FFFFFF,
				innerCutoutRadius = cutout
			)
			mat.pop()
			mat.push()
			val centreAngle = lerpAngle(range.start, range.endInclusive, 0.5F)
			val vec = Vector2f(cos(centreAngle), sin(centreAngle)).mul(40F)
			mat.translate(vec.x, vec.y, 0F)
			option.renderSlice(event.context)
			mat.pop()
		}
		event.context.drawLine(1, 1, delta.x.toInt(), delta.y.toInt(), me.shedaniel.math.Color.ofOpaque(0x00FF00))
		mat.pop()
	}

	@Subscribe
	fun onTick(event: TickEvent) {
		val menu = activeMenu ?: return
		if (!menu.key.isPressed(true)) {
			val angle = atan2(delta.y, delta.x)

			val choiceIndex = (wrapAngle(angle) * menu.options.size / τ).toInt()
			val choice = menu.options[choiceIndex]
			val selectedAny = delta.lengthSquared() > INNER_CIRCLE_RADIUS * INNER_CIRCLE_RADIUS
			activeMenu = null
			if (selectedAny)
				choice.resolve()
		}
	}

}

object RadialMacros {
	var wheels = MacroData.DConfig.data.wheels
		private set

	fun setWheels(wheels: List<MacroWheel>) {
		this.wheels = wheels
		RadialMenuViewer.activeMenu = null
	}

	@Subscribe
	fun onOpen(event: WorldKeyboardEvent) {
		if (RadialMenuViewer.activeMenu != null) return
		wheels.forEach { wheel ->
			if (event.matches(wheel.key, atLeast = true)) {
				class R(val action: HotkeyAction) : RadialMenuOption {
					override val isEnabled: Boolean
						get() = true

					override fun resolve() {
						action.execute()
					}

					override fun renderSlice(drawContext: DrawContext) {
						drawContext.drawCenteredTextWithShadow(MC.font, action.label, 0, 0, -1)
					}
				}
				RadialMenuViewer.activeMenu = object : RadialMenu {
					override val key: SavedKeyBinding
						get() = wheel.key
					override val options: List<RadialMenuOption> =
						wheel.options.map { R(it) }
				}
			}
		}
	}
}