Skip to content

Commit b59803c

Browse files
committed
feat(ui): replace VariablePanel with FileTreePanel #453
Replace the VariablePanel component with a new FileTreePanel that provides project file browsing capabilities. Add FileTreeCellRenderer for custom file icons, implement project loading and file selection functionality. Update DevInsEditorFrame to support dynamic UI layout switching based on file type, with improved syntax highlighting for multiple languages. Integrate FlatLaf dark theme for modern UI appearance and add support for opening projects and individual files.
1 parent 9d971b2 commit b59803c

File tree

5 files changed

+436
-202
lines changed

5 files changed

+436
-202
lines changed

mpp-ui/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ dependencies {
1414
// 用于代码编辑器功能
1515
implementation("com.fifesoft:rsyntaxtextarea:3.3.4")
1616

17+
// 现代化 UI 主题
18+
implementation("com.formdev:flatlaf:3.2.5")
19+
implementation("com.formdev:flatlaf-extras:3.2.5")
20+
1721
// JSON 处理
1822
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
1923

mpp-ui/src/main/kotlin/cc/unitmesh/devins/ui/Main.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
11
package cc.unitmesh.devins.ui
22

33
import cc.unitmesh.devins.ui.swing.DevInsEditorFrame
4+
import com.formdev.flatlaf.FlatDarkLaf
5+
import com.formdev.flatlaf.extras.FlatSVGIcon
46
import javax.swing.SwingUtilities
57
import javax.swing.UIManager
68

79
fun main() {
10+
// 设置 FlatLaf 暗色主题
11+
try {
12+
FlatDarkLaf.setup()
13+
14+
// 设置一些自定义属性
15+
UIManager.put("Tree.showDefaultIcons", true)
16+
UIManager.put("Component.focusWidth", 1)
17+
UIManager.put("ScrollBar.showButtons", false)
18+
UIManager.put("ScrollBar.width", 12)
19+
20+
} catch (e: Exception) {
21+
e.printStackTrace()
22+
}
23+
824
SwingUtilities.invokeLater {
925
DevInsEditorFrame().isVisible = true
1026
}

mpp-ui/src/main/kotlin/cc/unitmesh/devins/ui/swing/DevInsEditorFrame.kt

Lines changed: 165 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,27 @@ import javax.swing.*
1313
import javax.swing.border.TitledBorder
1414
import javax.swing.filechooser.FileNameExtensionFilter
1515

16-
class DevInsEditorFrame : JFrame("DevIns Editor") {
17-
16+
class DevInsEditorFrame : JFrame("AutoDev Desktop") {
17+
1818
private val sourceEditor = RSyntaxTextArea(20, 60)
1919
private val outputArea = JTextArea(10, 60)
20-
private val variablePanel = VariablePanel()
2120
private val statusLabel = JLabel("Ready")
2221
private val compileButton = JButton("Compile")
2322
private val clearButton = JButton("Clear")
24-
23+
private val fileTreePanel = FileTreePanel { file -> openFileInEditor(file) }
24+
2525
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
2626
private var currentFile: File? = null
27+
private var mainPanel: JSplitPane? = null
28+
private var outputPanel: JComponent? = null
2729

2830
init {
2931
setupUI()
3032
setupActions()
3133
setupDefaultContent()
3234

3335
defaultCloseOperation = EXIT_ON_CLOSE
34-
setSize(1200, 800)
36+
setSize(1400, 900)
3537
setLocationRelativeTo(null)
3638
}
3739

@@ -42,12 +44,8 @@ class DevInsEditorFrame : JFrame("DevIns Editor") {
4244
val toolbar = createToolbar()
4345
add(toolbar, BorderLayout.NORTH)
4446

45-
// 主面板
46-
val mainPanel = JSplitPane(JSplitPane.HORIZONTAL_SPLIT)
47-
mainPanel.leftComponent = createLeftPanel()
48-
mainPanel.rightComponent = createRightPanel()
49-
mainPanel.dividerLocation = 800
50-
add(mainPanel, BorderLayout.CENTER)
47+
// 初始化主面板
48+
setupMainPanel(false)
5149

5250
// 状态栏
5351
val statusPanel = JPanel(FlowLayout(FlowLayout.LEFT))
@@ -60,46 +58,46 @@ class DevInsEditorFrame : JFrame("DevIns Editor") {
6058
toolbar.isFloatable = false
6159

6260
// 文件操作
63-
val openButton = JButton("Open")
64-
openButton.addActionListener { openFile() }
65-
toolbar.add(openButton)
61+
val openProjectButton = JButton("Open Project")
62+
openProjectButton.addActionListener { openProject() }
63+
toolbar.add(openProjectButton)
64+
65+
val openFileButton = JButton("Open File")
66+
openFileButton.addActionListener { openFile() }
67+
toolbar.add(openFileButton)
6668

6769
val saveButton = JButton("Save")
6870
saveButton.addActionListener { saveFile() }
6971
toolbar.add(saveButton)
7072

7173
toolbar.addSeparator()
7274

73-
// 编译操作
75+
// 编译操作(初始隐藏)
76+
compileButton.isVisible = false
77+
clearButton.isVisible = false
7478
toolbar.add(compileButton)
7579
toolbar.add(clearButton)
7680

7781
return toolbar
7882
}
7983

80-
private fun createLeftPanel(): JComponent {
81-
val leftPanel = JSplitPane(JSplitPane.VERTICAL_SPLIT)
82-
84+
private fun createEditorPanel(): JComponent {
8385
// 代码编辑器
84-
sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_MARKDOWN
86+
sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_NONE
8587
sourceEditor.isCodeFoldingEnabled = true
8688
sourceEditor.antiAliasingEnabled = true
8789
sourceEditor.font = Font("JetBrains Mono", Font.PLAIN, 14)
8890
sourceEditor.tabSize = 2
89-
91+
9092
val editorScrollPane = RTextScrollPane(sourceEditor)
9193
editorScrollPane.lineNumbersEnabled = true
9294
editorScrollPane.isFoldIndicatorEnabled = true
93-
editorScrollPane.border = TitledBorder("DevIns Source")
94-
95-
leftPanel.topComponent = editorScrollPane
96-
leftPanel.bottomComponent = variablePanel
97-
leftPanel.dividerLocation = 400
98-
99-
return leftPanel
95+
editorScrollPane.border = TitledBorder("📝 Editor")
96+
97+
return editorScrollPane
10098
}
10199

102-
private fun createRightPanel(): JComponent {
100+
private fun createOutputPanel(): JComponent {
103101
outputArea.isEditable = false
104102
outputArea.font = Font("JetBrains Mono", Font.PLAIN, 12)
105103
outputArea.background = Color.WHITE
@@ -128,29 +126,29 @@ class DevInsEditorFrame : JFrame("DevIns Editor") {
128126
variables:
129127
greeting: "Hello"
130128
target: "World"
129+
author: "DevIns Team"
130+
version: "1.0.0"
131131
---
132-
132+
133133
# DevIns Template Example
134-
134+
135135
${'$'}greeting, ${'$'}target! Welcome to DevIns.
136-
136+
137137
This is a simple example showing:
138138
- Variable substitution: ${'$'}greeting and ${'$'}target
139139
- Front matter configuration
140140
- Markdown-like syntax
141-
141+
142142
## Variables in Action
143-
143+
144144
You can use variables like ${'$'}greeting anywhere in your template.
145145
The compiler will replace them with the actual values.
146-
147-
Try editing the variables in the panel below!
146+
147+
Edit the variables in the front matter above to see changes!
148+
149+
Author: ${'$'}author
150+
Version: ${'$'}version
148151
""".trimIndent()
149-
150-
variablePanel.addVariable("greeting", "Hello")
151-
variablePanel.addVariable("target", "World")
152-
variablePanel.addVariable("author", "DevIns Team")
153-
variablePanel.addVariable("version", "1.0.0")
154152
}
155153

156154
private fun compile() {
@@ -162,10 +160,7 @@ class DevInsEditorFrame : JFrame("DevIns Editor") {
162160
val startTime = System.currentTimeMillis()
163161

164162
val result = withContext(Dispatchers.IO) {
165-
DevInsCompilerFacade.compile(
166-
sourceEditor.text,
167-
variablePanel.getVariables()
168-
)
163+
DevInsCompilerFacade.compile(sourceEditor.text)
169164
}
170165

171166
val executionTime = System.currentTimeMillis() - startTime
@@ -200,25 +195,14 @@ class DevInsEditorFrame : JFrame("DevIns Editor") {
200195

201196
private fun openFile() {
202197
val fileChooser = JFileChooser().apply {
203-
fileFilter = FileNameExtensionFilter("DevIns Files", "devin", "devins", "txt")
198+
fileFilter = FileNameExtensionFilter("All Supported Files",
199+
"devin", "devins", "kt", "java", "js", "ts", "py", "json", "yaml", "yml", "md", "txt")
204200
currentDirectory = File(System.getProperty("user.home"))
205201
}
206-
202+
207203
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
208204
val file = fileChooser.selectedFile
209-
try {
210-
sourceEditor.text = file.readText()
211-
currentFile = file
212-
title = "DevIns Editor - ${file.name}"
213-
statusLabel.text = "Opened: ${file.name}"
214-
} catch (e: Exception) {
215-
JOptionPane.showMessageDialog(
216-
this,
217-
"Failed to open file: ${e.message}",
218-
"Error",
219-
JOptionPane.ERROR_MESSAGE
220-
)
221-
}
205+
openFileInEditor(file)
222206
}
223207
}
224208

@@ -251,4 +235,126 @@ class DevInsEditorFrame : JFrame("DevIns Editor") {
251235
)
252236
}
253237
}
238+
239+
private fun setupMainPanel(showOutput: Boolean) {
240+
// 移除现有的主面板
241+
if (mainPanel != null) {
242+
remove(mainPanel)
243+
}
244+
245+
mainPanel = JSplitPane(JSplitPane.HORIZONTAL_SPLIT)
246+
247+
// 左侧:文件树 + 编辑器
248+
val leftPanel = JSplitPane(JSplitPane.HORIZONTAL_SPLIT)
249+
leftPanel.leftComponent = fileTreePanel
250+
leftPanel.rightComponent = createEditorPanel()
251+
leftPanel.dividerLocation = 250
252+
leftPanel.resizeWeight = 0.0 // 文件树固定宽度
253+
254+
if (showOutput) {
255+
// 三栏布局:文件树 + 编辑器 + 输出
256+
outputPanel = createOutputPanel()
257+
mainPanel!!.leftComponent = leftPanel
258+
mainPanel!!.rightComponent = outputPanel
259+
mainPanel!!.dividerLocation = 900
260+
mainPanel!!.resizeWeight = 0.7
261+
} else {
262+
// 两栏布局:文件树 + 编辑器
263+
mainPanel!!.leftComponent = leftPanel
264+
mainPanel!!.rightComponent = null
265+
mainPanel!!.dividerLocation = 0
266+
mainPanel!!.resizeWeight = 1.0
267+
}
268+
269+
add(mainPanel, BorderLayout.CENTER)
270+
revalidate()
271+
repaint()
272+
}
273+
274+
private fun isDevInsFile(file: File): Boolean {
275+
val extension = file.extension.lowercase()
276+
return extension in setOf("devin", "devins")
277+
}
278+
279+
private fun updateUIForFileType(file: File) {
280+
val isDevIns = isDevInsFile(file)
281+
282+
// 更新工具栏按钮可见性
283+
compileButton.isVisible = isDevIns
284+
clearButton.isVisible = isDevIns
285+
286+
// 更新编辑器标题
287+
val editorScrollPane = sourceEditor.parent.parent as RTextScrollPane
288+
val fileIcon = when {
289+
isDevIns -> "📄"
290+
file.extension.lowercase() in setOf("kt", "java") -> ""
291+
file.extension.lowercase() in setOf("js", "ts") -> "🟨"
292+
file.extension.lowercase() in setOf("py") -> "🐍"
293+
file.extension.lowercase() in setOf("json", "yaml", "yml") -> "⚙️"
294+
file.extension.lowercase() in setOf("md") -> "📝"
295+
else -> "📄"
296+
}
297+
298+
val title = if (isDevIns) "$fileIcon DevIns Source" else "$fileIcon ${file.name}"
299+
editorScrollPane.border = TitledBorder(title)
300+
301+
// 更新布局
302+
setupMainPanel(isDevIns)
303+
304+
// 重新绘制工具栏
305+
(compileButton.parent as JToolBar).revalidate()
306+
(compileButton.parent as JToolBar).repaint()
307+
}
308+
309+
private fun openProject() {
310+
val fileChooser = JFileChooser().apply {
311+
fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
312+
dialogTitle = "Select Project Directory"
313+
currentDirectory = File(System.getProperty("user.home"))
314+
}
315+
316+
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
317+
val selectedDir = fileChooser.selectedFile
318+
fileTreePanel.loadProject(selectedDir)
319+
statusLabel.text = "Opened project: ${selectedDir.name}"
320+
}
321+
}
322+
323+
private fun openFileInEditor(file: File) {
324+
try {
325+
val content = file.readText()
326+
sourceEditor.text = content
327+
currentFile = file
328+
title = "DevIns Editor - ${file.name}"
329+
statusLabel.text = "Opened: ${file.name}"
330+
331+
// 根据文件扩展名设置语法高亮
332+
when (file.extension.lowercase()) {
333+
"devin", "devins" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_MARKDOWN
334+
"md" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_MARKDOWN
335+
"kt" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_KOTLIN
336+
"java" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_JAVA
337+
"js" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT
338+
"ts" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT
339+
"py" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_PYTHON
340+
"json" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_JSON
341+
"yaml", "yml" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_YAML
342+
"xml" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_XML
343+
"html" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_HTML
344+
"css" -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_CSS
345+
else -> sourceEditor.syntaxEditingStyle = SyntaxConstants.SYNTAX_STYLE_NONE
346+
}
347+
348+
// 根据文件类型更新 UI
349+
updateUIForFileType(file)
350+
351+
} catch (e: Exception) {
352+
JOptionPane.showMessageDialog(
353+
this,
354+
"Failed to open file: ${e.message}",
355+
"Error",
356+
JOptionPane.ERROR_MESSAGE
357+
)
358+
}
359+
}
254360
}

0 commit comments

Comments
 (0)