1
1
package cc.unitmesh.devti.gui.planner
2
2
3
3
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.*
13
5
import java.awt.event.ActionEvent
14
6
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
18
10
import javax.swing.border.LineBorder
19
- import javax.swing.text.SimpleAttributeSet
11
+ import javax.swing.plaf.basic.BasicProgressBarUI
12
+ import javax.swing.text.BadLocationException
20
13
import javax.swing.text.StyleConstants
21
14
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 >(
28
17
" 🤔 Analyzing your request..." ,
29
18
" 💡 Generating a plan..." ,
30
19
" ⚙️ Processing the steps..." ,
31
20
" ✨ Almost there..."
32
21
)
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 )
34
51
35
52
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 )
39
56
40
- val containerPanel = object : JPanel (BorderLayout ()) {
57
+ glassPanel = object : JPanel (BorderLayout ()) {
41
58
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
45
60
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
46
79
47
80
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
52
83
)
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()
55
94
}
56
95
}
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
- }
68
96
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 )
71
99
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()
74
127
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
84
174
currentText = " "
85
- currentTextIndex = (currentTextIndex + 1 ) % loadingTexts.size
175
+ messageIndex = (messageIndex + 1 ) % loadingMessages.size
176
+ updateEmojiLabel()
177
+
178
+ transitionTimer.start()
86
179
}
87
180
}
181
+ glassPanel.repaint()
88
182
})
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()
90
200
}
91
201
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)
97
236
StyleConstants .setFontFamily(style, " Monospaced" )
237
+ StyleConstants .setFontSize(style, 14 )
98
238
99
239
try {
100
- doc.remove(0 , doc.length )
240
+ doc.remove(0 , doc.getLength() )
101
241
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()
104
247
}
105
248
}
106
249
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
+
107
269
override fun removeNotify () {
108
270
super .removeNotify()
109
- timer.stop()
271
+ typingTimer.stop()
272
+ transitionTimer.stop()
273
+ gradientTimer.stop()
110
274
}
111
275
}
0 commit comments