Skip to content

Commit b650ad4

Browse files
committed
feat(gui): redesign LoadingPanel with advanced animation and dark mode support #352
- Implement background - Add typing animation for loading new UI design with rounded corners and gradient messages - Introduce dark mode toggle and dynamic color scheme - Improve message display and add emoji support - Optimize performance with efficient rendering and timers
1 parent 18c4b2f commit b650ad4

File tree

1 file changed

+232
-68
lines changed

1 file changed

+232
-68
lines changed
Lines changed: 232 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,275 @@
11
package cc.unitmesh.devti.gui.planner
22

33
import com.intellij.openapi.project.Project
4-
import com.intellij.util.ui.JBUI
5-
import com.intellij.util.ui.UIUtil
6-
import java.awt.BorderLayout
7-
import java.awt.Color
8-
import java.awt.Dimension
9-
import java.awt.GradientPaint
10-
import java.awt.Graphics
11-
import java.awt.Graphics2D
12-
import java.awt.RenderingHints
4+
import java.awt.*
135
import java.awt.event.ActionEvent
146
import java.awt.event.ActionListener
15-
import javax.swing.JPanel
16-
import javax.swing.JTextPane
17-
import javax.swing.Timer
7+
import java.awt.geom.RoundRectangle2D
8+
import javax.swing.*
9+
import javax.swing.border.EmptyBorder
1810
import javax.swing.border.LineBorder
19-
import javax.swing.text.SimpleAttributeSet
11+
import javax.swing.plaf.basic.BasicProgressBarUI
12+
import javax.swing.text.BadLocationException
2013
import javax.swing.text.StyleConstants
2114

22-
class LoadingPanel(project: Project) : JPanel(BorderLayout()) {
23-
private val textPane = JTextPane()
24-
private val timer = Timer(50, null)
25-
private var currentText = ""
26-
private var currentIndex = 0
27-
private val loadingTexts = listOf(
15+
class LoadingPanel(val project: Project) : JPanel() {
16+
private val loadingMessages: MutableList<String> = mutableListOf<String>(
2817
"🤔 Analyzing your request...",
2918
"💡 Generating a plan...",
3019
"⚙️ Processing the steps...",
3120
"✨ Almost there..."
3221
)
33-
private var currentTextIndex = 0
22+
23+
private val messagePane: JTextPane
24+
private val progressBar: JProgressBar
25+
private val emojiLabel: JLabel
26+
private val contentPanel: JPanel
27+
private val glassPanel: JPanel
28+
29+
private val typingTimer: Timer
30+
private val transitionTimer: Timer
31+
private var currentText = ""
32+
private var charIndex = 0
33+
private var messageIndex = 0
34+
private var opacity = 0.0f
35+
private var fadeIn = true
36+
private var isDarkMode = false
37+
38+
private val gradientTimer: Timer
39+
private var gradientPosition = 0.0f
40+
private val gradientSpeed = 0.005f
41+
42+
private val lightBackground = Color(245, 247, 250)
43+
private val darkBackground = Color(30, 32, 40)
44+
private val lightForeground = Color(50, 50, 50)
45+
private val darkForeground = Color(220, 220, 220)
46+
private val lightBorder = Color(200, 210, 230)
47+
private val darkBorder = Color(60, 65, 80)
48+
private val progressColor = Color(59, 130, 246)
49+
private val progressBackground = Color(229, 231, 235)
50+
private val darkProgressBackground = Color(55, 65, 81)
3451

3552
init {
36-
background = JBUI.CurrentTheme.ToolWindow.background()
37-
border = JBUI.Borders.empty(10)
38-
preferredSize = Dimension(0, JBUI.scale(60))
53+
setLayout(BorderLayout())
54+
setBorder(EmptyBorder(20, 20, 20, 20))
55+
setOpaque(false)
3956

40-
val containerPanel = object : JPanel(BorderLayout()) {
57+
glassPanel = object : JPanel(BorderLayout()) {
4158
override fun paintComponent(g: Graphics) {
42-
super.paintComponent(g)
43-
val g2d = g as Graphics2D
44-
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
59+
val g2d = g.create() as Graphics2D
4560
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
61+
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
62+
63+
val width = getWidth()
64+
val height = getHeight()
65+
66+
67+
// Create rounded rectangle for the panel
68+
val roundedRect: RoundRectangle2D =
69+
RoundRectangle2D.Float(0f, 0f, width.toFloat(), height.toFloat(), 15f, 15f)
70+
g2d.setClip(roundedRect)
71+
72+
val color1 = if (isDarkMode) Color(30, 40, 70, 200) else Color(240, 245, 255, 200)
73+
val color2 = if (isDarkMode) Color(40, 30, 70, 200) else Color(245, 240, 255, 200)
74+
val color3 = if (isDarkMode) Color(35, 35, 60, 200) else Color(250, 245, 255, 200)
75+
76+
val pos1 = (gradientPosition) % 1.0f
77+
val pos2 = (gradientPosition + 0.33f) % 1.0f
78+
val pos3 = (gradientPosition + 0.66f) % 1.0f
4679

4780
val gradient = GradientPaint(
48-
0f, 0f,
49-
JBUI.CurrentTheme.ToolWindow.background(),
50-
0f, height.toFloat(),
51-
JBUI.CurrentTheme.ToolWindow.background().darker()
81+
width * pos1, 0f, color1,
82+
width * pos2, height.toFloat(), color2
5283
)
53-
g2d.paint = gradient
54-
g2d.fillRect(0, 0, width, height)
84+
85+
g2d.setPaint(gradient)
86+
g2d.fill(roundedRect)
87+
88+
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f))
89+
g2d.setColor(Color.WHITE)
90+
g2d.fillRect(0, 0, width, height / 2)
91+
92+
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity))
93+
g2d.dispose()
5594
}
5695
}
57-
containerPanel.border = LineBorder(UIUtil.getBoundsColor(), 1, true)
58-
containerPanel.preferredSize = Dimension(0, JBUI.scale(50))
59-
60-
textPane.apply {
61-
background = Color(0, 0, 0, 0)
62-
foreground = UIUtil.getLabelForeground()
63-
font = JBUI.Fonts.create("Monospaced", 14)
64-
isEditable = false
65-
isOpaque = false
66-
border = JBUI.Borders.empty(10, 0)
67-
}
6896

69-
containerPanel.add(textPane, BorderLayout.CENTER)
70-
add(containerPanel, BorderLayout.CENTER)
97+
glassPanel.setBorder(LineBorder(if (isDarkMode) darkBorder else lightBorder, 1, true))
98+
glassPanel.setOpaque(false)
7199

72-
startTypingAnimation()
73-
}
100+
contentPanel = JPanel(BorderLayout(10, 10))
101+
contentPanel.setOpaque(false)
102+
contentPanel.setBorder(EmptyBorder(15, 15, 15, 15))
103+
104+
emojiLabel = JLabel()
105+
emojiLabel.setFont(Font("Segoe UI Emoji", Font.PLAIN, 24))
106+
emojiLabel.setHorizontalAlignment(SwingConstants.CENTER)
107+
emojiLabel.setPreferredSize(Dimension(40, 40))
108+
109+
messagePane = JTextPane()
110+
messagePane.setEditable(false)
111+
messagePane.setOpaque(false)
112+
messagePane.setFont(Font("Monospaced", Font.PLAIN, 14))
113+
messagePane.setBorder(null)
114+
115+
progressBar = JProgressBar(0, 100)
116+
progressBar.setStringPainted(false)
117+
progressBar.setBorderPainted(false)
118+
progressBar.setOpaque(false)
119+
progressBar.setPreferredSize(Dimension(0, 5))
120+
progressBar.setUI(object : BasicProgressBarUI() {
121+
override fun paintDeterminate(g: Graphics, c: JComponent) {
122+
val g2d = g.create() as Graphics2D
123+
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
124+
125+
val width = c.getWidth()
126+
val height = c.getHeight()
74127

75-
private fun startTypingAnimation() {
76-
timer.addActionListener(object : ActionListener {
77-
override fun actionPerformed(e: ActionEvent) {
78-
if (currentIndex < loadingTexts[currentTextIndex].length) {
79-
currentText += loadingTexts[currentTextIndex][currentIndex]
80-
updateText()
81-
currentIndex++
82-
} else {
83-
currentIndex = 0
128+
g2d.setColor(if (isDarkMode) darkProgressBackground else progressBackground)
129+
g2d.fillRoundRect(0, 0, width, height, height, height)
130+
131+
val progressWidth = (width * progressBar.getPercentComplete()).toInt()
132+
if (progressWidth > 0) {
133+
g2d.setColor(progressColor)
134+
g2d.fillRoundRect(0, 0, progressWidth, height, height, height)
135+
}
136+
137+
g2d.dispose()
138+
}
139+
140+
override fun paintIndeterminate(g: Graphics, c: JComponent) {
141+
paintDeterminate(g, c)
142+
}
143+
})
144+
145+
val textPanel = JPanel(BorderLayout(0, 5))
146+
textPanel.setOpaque(false)
147+
textPanel.add(messagePane, BorderLayout.CENTER)
148+
textPanel.add(progressBar, BorderLayout.SOUTH)
149+
150+
contentPanel.add(emojiLabel, BorderLayout.WEST)
151+
contentPanel.add(textPanel, BorderLayout.CENTER)
152+
153+
glassPanel.add(contentPanel, BorderLayout.CENTER)
154+
add(glassPanel, BorderLayout.CENTER)
155+
156+
typingTimer = Timer(60, ActionListener { e: ActionEvent? -> updateTypingAnimation() })
157+
transitionTimer = Timer(20, ActionListener { e: ActionEvent? ->
158+
if (fadeIn) {
159+
opacity += 0.05f
160+
if (opacity >= 1.0f) {
161+
opacity = 1.0f
162+
fadeIn = false
163+
transitionTimer.stop()
164+
typingTimer.start()
165+
}
166+
} else {
167+
opacity -= 0.05f
168+
if (opacity <= 0.0f) {
169+
opacity = 0.0f
170+
fadeIn = true
171+
transitionTimer.stop()
172+
173+
charIndex = 0
84174
currentText = ""
85-
currentTextIndex = (currentTextIndex + 1) % loadingTexts.size
175+
messageIndex = (messageIndex + 1) % loadingMessages.size
176+
updateEmojiLabel()
177+
178+
transitionTimer.start()
86179
}
87180
}
181+
glassPanel.repaint()
88182
})
89-
timer.start()
183+
184+
gradientTimer = Timer(50, ActionListener { e: ActionEvent? ->
185+
gradientPosition += gradientSpeed
186+
if (gradientPosition > 1.0f) {
187+
gradientPosition = 0.0f
188+
}
189+
glassPanel.repaint()
190+
})
191+
192+
updateEmojiLabel()
193+
startAnimations()
194+
195+
val darkModeToggle = JToggleButton("Dark Mode")
196+
darkModeToggle.addActionListener(ActionListener { e: ActionEvent? -> toggleDarkMode() })
197+
add(darkModeToggle, BorderLayout.SOUTH)
198+
199+
updateColors()
90200
}
91201

92-
private fun updateText() {
93-
val doc = textPane.styledDocument
94-
val style = SimpleAttributeSet()
95-
StyleConstants.setForeground(style, UIUtil.getLabelForeground())
96-
StyleConstants.setFontSize(style, 14)
202+
private fun startAnimations() {
203+
fadeIn = true
204+
opacity = 0.0f
205+
transitionTimer.start()
206+
gradientTimer.start()
207+
}
208+
209+
private fun updateTypingAnimation() {
210+
val message = loadingMessages.get(messageIndex)
211+
val textPart = message.substring(message.indexOf(' ') + 1)
212+
213+
if (charIndex < textPart.length) {
214+
currentText += textPart.get(charIndex)
215+
updateMessageText()
216+
charIndex++
217+
218+
val progress = ((charIndex.toFloat() / textPart.length) * 100).toInt()
219+
progressBar.setValue(progress)
220+
} else {
221+
typingTimer.stop()
222+
val pauseTimer = Timer(1200, ActionListener { e: ActionEvent? ->
223+
(e!!.getSource() as Timer).stop()
224+
fadeIn = false
225+
transitionTimer.start()
226+
})
227+
pauseTimer.setRepeats(false)
228+
pauseTimer.start()
229+
}
230+
}
231+
232+
private fun updateMessageText() {
233+
val doc = messagePane.getStyledDocument()
234+
val style = messagePane.addStyle("MessageStyle", null)
235+
StyleConstants.setForeground(style, if (isDarkMode) darkForeground else lightForeground)
97236
StyleConstants.setFontFamily(style, "Monospaced")
237+
StyleConstants.setFontSize(style, 14)
98238

99239
try {
100-
doc.remove(0, doc.length)
240+
doc.remove(0, doc.getLength())
101241
doc.insertString(0, currentText, style)
102-
} catch (e: Exception) {
103-
// Ignore any document modification errors
242+
243+
StyleConstants.setForeground(style, progressColor)
244+
doc.insertString(doc.getLength(), "|", style)
245+
} catch (e: BadLocationException) {
246+
e.printStackTrace()
104247
}
105248
}
106249

250+
private fun updateEmojiLabel() {
251+
val message = loadingMessages.get(messageIndex)
252+
val emoji = message.substring(0, message.indexOf(' '))
253+
emojiLabel.setText(emoji)
254+
}
255+
256+
private fun toggleDarkMode() {
257+
isDarkMode = !isDarkMode
258+
updateColors()
259+
glassPanel.repaint()
260+
}
261+
262+
private fun updateColors() {
263+
setBackground(if (isDarkMode) darkBackground else lightBackground)
264+
messagePane.setForeground(if (isDarkMode) darkForeground else lightForeground)
265+
glassPanel.setBorder(LineBorder(if (isDarkMode) darkBorder else lightBorder, 1, true))
266+
updateMessageText()
267+
}
268+
107269
override fun removeNotify() {
108270
super.removeNotify()
109-
timer.stop()
271+
typingTimer.stop()
272+
transitionTimer.stop()
273+
gradientTimer.stop()
110274
}
111275
}

0 commit comments

Comments
 (0)