Skip to content

Commit c1eec6f

Browse files
committed
feat(gui): implement a loading panel with animation for AutoDev Planner #352
- Add a new LoadingPanel class to display loading messages with animation - Integrate the LoadingPanel into the AutoDevPlannerToolWindow - Implement typing animation for loading messages - Update UI styling for the loading panel
1 parent c857509 commit c1eec6f

File tree

1 file changed

+110
-9
lines changed

1 file changed

+110
-9
lines changed

core/src/main/kotlin/cc/unitmesh/devti/gui/planner/AutoDevPlannerToolWindow.kt

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ import com.intellij.openapi.wm.ToolWindowManager
1616
import com.intellij.ui.dsl.builder.panel
1717
import com.intellij.util.ui.JBUI
1818
import com.intellij.util.ui.UIUtil
19-
import java.awt.BorderLayout
20-
import javax.swing.JLabel
21-
import javax.swing.JPanel
19+
import java.awt.*
20+
import java.awt.event.ActionEvent
21+
import java.awt.event.ActionListener
22+
import javax.swing.*
23+
import javax.swing.border.EmptyBorder
24+
import javax.swing.border.LineBorder
25+
import javax.swing.text.SimpleAttributeSet
26+
import javax.swing.text.StyleConstants
27+
import javax.swing.text.StyledDocument
2228

2329
class AutoDevPlannerToolWindow(val project: Project) : SimpleToolWindowPanel(true, true), Disposable {
2430
override fun getName(): String = "AutoDev Planner"
@@ -50,6 +56,11 @@ class AutoDevPlannerToolWindow(val project: Project) : SimpleToolWindowPanel(tru
5056
if (!isEditorMode && !isIssueInputMode) {
5157
runInEdt {
5258
planLangSketch.updatePlan(items)
59+
contentPanel.components.find { it is LoadingPanel }?.let {
60+
contentPanel.remove(it)
61+
contentPanel.revalidate()
62+
contentPanel.repaint()
63+
}
5364
}
5465
}
5566
}
@@ -156,10 +167,7 @@ class AutoDevPlannerToolWindow(val project: Project) : SimpleToolWindowPanel(tru
156167
contentPanel.removeAll()
157168
contentPanel.add(planPanel, BorderLayout.CENTER)
158169

159-
val loadingPanel = JPanel(BorderLayout()).apply {
160-
add(JLabel("Processing your request..."), BorderLayout.NORTH)
161-
background = JBUI.CurrentTheme.ToolWindow.background()
162-
}
170+
val loadingPanel = LoadingPanel(project)
163171
contentPanel.add(loadingPanel, BorderLayout.NORTH)
164172

165173
contentPanel.revalidate()
@@ -176,7 +184,8 @@ class AutoDevPlannerToolWindow(val project: Project) : SimpleToolWindowPanel(tru
176184
companion object {
177185
fun showPlanEditor(project: Project, planText: String, callback: (String) -> Unit) {
178186
val toolWindow =
179-
ToolWindowManager.Companion.getInstance(project).getToolWindow(AutoDevPlannerToolWindowFactory.Companion.PlANNER_ID)
187+
ToolWindowManager.Companion.getInstance(project)
188+
.getToolWindow(AutoDevPlannerToolWindowFactory.Companion.PlANNER_ID)
180189
if (toolWindow != null) {
181190
val content = toolWindow.contentManager.getContent(0)
182191
val plannerWindow = content?.component as? AutoDevPlannerToolWindow
@@ -195,7 +204,8 @@ class AutoDevPlannerToolWindow(val project: Project) : SimpleToolWindowPanel(tru
195204

196205
fun showIssueInput(project: Project) {
197206
val toolWindow = ToolWindowManager.Companion.getInstance(project).getToolWindow(
198-
AutoDevPlannerToolWindowFactory.Companion.PlANNER_ID)
207+
AutoDevPlannerToolWindowFactory.Companion.PlANNER_ID
208+
)
199209
if (toolWindow == null) return
200210

201211
val content = toolWindow.contentManager.getContent(0)
@@ -208,3 +218,94 @@ class AutoDevPlannerToolWindow(val project: Project) : SimpleToolWindowPanel(tru
208218
}
209219
}
210220
}
221+
222+
private class LoadingPanel(project: Project) : JPanel(BorderLayout()) {
223+
private val textPane = JTextPane()
224+
private val timer = Timer(50, null)
225+
private var currentText = ""
226+
private var currentIndex = 0
227+
private val loadingTexts = listOf(
228+
"🤔 Analyzing your request...",
229+
"💡 Generating a plan...",
230+
"⚙️ Processing the steps...",
231+
"✨ Almost there..."
232+
)
233+
private var currentTextIndex = 0
234+
235+
init {
236+
background = JBUI.CurrentTheme.ToolWindow.background()
237+
border = JBUI.Borders.empty(10)
238+
preferredSize = Dimension(0, JBUI.scale(60))
239+
240+
val containerPanel = object : JPanel(BorderLayout()) {
241+
override fun paintComponent(g: Graphics) {
242+
super.paintComponent(g)
243+
val g2d = g as Graphics2D
244+
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
245+
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
246+
247+
val gradient = GradientPaint(
248+
0f, 0f,
249+
JBUI.CurrentTheme.ToolWindow.background(),
250+
0f, height.toFloat(),
251+
JBUI.CurrentTheme.ToolWindow.background().darker()
252+
)
253+
g2d.paint = gradient
254+
g2d.fillRect(0, 0, width, height)
255+
}
256+
}
257+
containerPanel.border = LineBorder(UIUtil.getBoundsColor(), 1, true)
258+
containerPanel.preferredSize = Dimension(0, JBUI.scale(50))
259+
260+
textPane.apply {
261+
background = Color(0, 0, 0, 0)
262+
foreground = UIUtil.getLabelForeground()
263+
font = JBUI.Fonts.create("Monospaced", 14)
264+
isEditable = false
265+
isOpaque = false
266+
border = JBUI.Borders.empty(10, 0)
267+
}
268+
269+
containerPanel.add(textPane, BorderLayout.CENTER)
270+
add(containerPanel, BorderLayout.CENTER)
271+
272+
startTypingAnimation()
273+
}
274+
275+
private fun startTypingAnimation() {
276+
timer.addActionListener(object : ActionListener {
277+
override fun actionPerformed(e: ActionEvent) {
278+
if (currentIndex < loadingTexts[currentTextIndex].length) {
279+
currentText += loadingTexts[currentTextIndex][currentIndex]
280+
updateText()
281+
currentIndex++
282+
} else {
283+
currentIndex = 0
284+
currentText = ""
285+
currentTextIndex = (currentTextIndex + 1) % loadingTexts.size
286+
}
287+
}
288+
})
289+
timer.start()
290+
}
291+
292+
private fun updateText() {
293+
val doc = textPane.styledDocument
294+
val style = SimpleAttributeSet()
295+
StyleConstants.setForeground(style, UIUtil.getLabelForeground())
296+
StyleConstants.setFontSize(style, 14)
297+
StyleConstants.setFontFamily(style, "Monospaced")
298+
299+
try {
300+
doc.remove(0, doc.length)
301+
doc.insertString(0, currentText, style)
302+
} catch (e: Exception) {
303+
// Ignore any document modification errors
304+
}
305+
}
306+
307+
override fun removeNotify() {
308+
super.removeNotify()
309+
timer.stop()
310+
}
311+
}

0 commit comments

Comments
 (0)