Skip to content

[Property Editor] Enable Property Editor panel #8108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions flutter-idea/src/io/flutter/dart/DartPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
package io.flutter.dart;

import com.intellij.execution.configurations.ConfigurationType;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
Expand Down Expand Up @@ -61,6 +64,12 @@ public static boolean isDartTestConfiguration(@NotNull ConfigurationType type) {
return type.getId().equals("DartTestRunConfigurationType");
}

public static DartPluginVersion getDartPluginVersion() {
final IdeaPluginDescriptor dartPlugin = PluginManagerCore.getPlugin(PluginId.getId("Dart"));
final String versionString = dartPlugin.getVersion();
return new DartPluginVersion(versionString);
}

/**
* Return the {@link DartAnalysisServerService} instance for the passed {@link Project}.
*/
Expand Down
46 changes: 46 additions & 0 deletions flutter-idea/src/io/flutter/dart/DartPluginVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2025 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
package io.flutter.dart;

import com.intellij.openapi.util.Version;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class DartPluginVersion implements Comparable<DartPluginVersion> {

@Nullable
private final String rawVersionString;

@Nullable
private final Version version;

public DartPluginVersion(@Nullable String versionString) {
rawVersionString = versionString;
version = Version.parseVersion(versionString);
}

@Override
public int compareTo(@NotNull DartPluginVersion otherVersion) {
if (rawVersionString == null) return -1;
if (otherVersion.rawVersionString == null) return 1;
return version.compareTo(otherVersion.version);
}

public boolean supportsPropertyEditor() {
if (version == null) return false;
final int major = version.major;

if (major == 243) {
return this.compareTo(new DartPluginVersion("243.26753.1")) >= 0;
}

if (major == 251) {
return this.compareTo(new DartPluginVersion("251.23774.318")) >= 0;
}

return major >= 244;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
import io.flutter.FlutterUtils;
import io.flutter.actions.RefreshToolWindowAction;
import io.flutter.bazel.WorkspaceCache;
import io.flutter.dart.DartPlugin;
import io.flutter.dart.DartPluginVersion;
import io.flutter.devtools.DevToolsIdeFeature;
import io.flutter.devtools.DevToolsUrl;
import io.flutter.run.daemon.DevToolsService;
import io.flutter.sdk.FlutterSdk;
import io.flutter.sdk.FlutterSdkVersion;
import io.flutter.utils.AsyncUtils;
import io.flutter.view.ViewUtils;
import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;

Expand All @@ -27,6 +30,9 @@
public class PropertyEditorViewFactory implements ToolWindowFactory {
@NotNull private static String TOOL_WINDOW_ID = "Flutter Property Editor";

@NotNull
private final ViewUtils viewUtils = new ViewUtils();

@Override
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
Expand All @@ -39,6 +45,12 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();

DartPluginVersion dartPluginVersion = DartPlugin.getDartPluginVersion();
if (!dartPluginVersion.supportsPropertyEditor()) {
viewUtils.presentLabel(toolWindow, "Flutter Property Editor requires a newer version of the Dart plugin.");
return;
}

AsyncUtils.whenCompleteUiThread(
DevToolsService.getInstance(project).getDevToolsInstance(),
(instance, error) -> {
Expand Down
2 changes: 1 addition & 1 deletion flutter-idea/src/io/flutter/sdk/FlutterSdkVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public final class FlutterSdkVersion implements Comparable<FlutterSdkVersion> {
private static final FlutterSdkVersion MIN_SUPPORTS_DTD = new FlutterSdkVersion("3.22.0");

@NotNull
private static final FlutterSdkVersion MIN_SUPPORTS_PROPERTY_EDITOR = new FlutterSdkVersion("3.29.0");
public static final FlutterSdkVersion MIN_SUPPORTS_PROPERTY_EDITOR = new FlutterSdkVersion("3.32.0-0.1.pre");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I will leave it to you if this changes at all with cherry picks.


@Nullable
private final Version version;
Expand Down
56 changes: 15 additions & 41 deletions flutter-idea/src/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ public class FlutterView implements PersistentStateComponent<FlutterViewState>,
@NotNull
private final FlutterViewState state = new FlutterViewState();

@VisibleForTesting
@NotNull
public final ViewUtils viewUtils;

@NotNull
private final Project myProject;

Expand All @@ -97,15 +101,16 @@ public class FlutterView implements PersistentStateComponent<FlutterViewState>,
private final JxBrowserManager jxBrowserManager;

public FlutterView(@NotNull Project project) {
this(project, JxBrowserManager.getInstance(), new JxBrowserUtils());
this(project, JxBrowserManager.getInstance(), new JxBrowserUtils(), new ViewUtils());
}

@VisibleForTesting
@NonInjectable
protected FlutterView(@NotNull Project project, @NotNull JxBrowserManager jxBrowserManager, JxBrowserUtils jxBrowserUtils) {
protected FlutterView(@NotNull Project project, @NotNull JxBrowserManager jxBrowserManager, JxBrowserUtils jxBrowserUtils, ViewUtils viewUtils) {
myProject = project;
this.jxBrowserUtils = jxBrowserUtils;
this.jxBrowserManager = jxBrowserManager;
this.viewUtils = viewUtils != null ? viewUtils : new ViewUtils();

shouldAutoHorizontalScroll.listen(state::setShouldAutoScroll);
highlightNodesShownInBothTrees.listen(state::setHighlightNodesShownInBothTrees);
Expand Down Expand Up @@ -194,7 +199,7 @@ private void addBrowserInspectorViewContent(FlutterApp app,
new LabelInput("The embedded browser failed to load. Error: " + error),
openDevToolsLabel(app, toolWindow, ideFeature)
);
presentClickableLabel(toolWindow, inputs);
viewUtils.presentClickableLabel(toolWindow, inputs);
});
}));
};
Expand All @@ -219,7 +224,7 @@ private void addBrowserInspectorViewContent(FlutterApp app,
.getUrlString(),
null
);
presentLabel(toolWindow, "DevTools inspector has been opened in the browser.");
viewUtils.presentLabel(toolWindow, "DevTools inspector has been opened in the browser.");
}
}

Expand Down Expand Up @@ -249,7 +254,7 @@ private void presentDevTools(FlutterApp app, ToolWindow toolWindow, boolean isEm
verifyEventDispatchThread();

devToolsInstallCount += 1;
presentLabel(toolWindow, getInstallingDevtoolsLabel());
viewUtils.presentLabel(toolWindow, getInstallingDevtoolsLabel());

openInspectorWithDevTools(app, toolWindow, isEmbedded, ideFeature);

Expand All @@ -268,7 +273,7 @@ protected void setUpToolWindowListener(FlutterApp app, ToolWindow toolWindow, bo
}
this.toolWindowListener.updateOnWindowOpen(() -> {
devToolsInstallCount += 1;
presentLabel(toolWindow, getInstallingDevtoolsLabel());
viewUtils.presentLabel(toolWindow, getInstallingDevtoolsLabel());
openInspectorWithDevTools(app, toolWindow, isEmbedded, ideFeature, true);
});
}
Expand Down Expand Up @@ -301,12 +306,12 @@ private void openInspectorWithDevTools(FlutterApp app,
// TODO(helinx): Restart DevTools server if there's an error.
if (error != null) {
LOG.error(error);
presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
viewUtils.presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
return;
}

if (instance == null) {
presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
viewUtils.presentLabel(toolWindow, DEVTOOLS_FAILED_LABEL);
return;
}

Expand Down Expand Up @@ -404,47 +409,16 @@ else if (latestFailureReason != null && Objects.equals(latestFailureReason.failu
inputs.add(openDevToolsLabel);
}

presentClickableLabel(toolWindow, inputs);
}

protected void presentLabel(ToolWindow toolWindow, String text) {
final JBLabel label = new JBLabel(text, SwingConstants.CENTER);
label.setForeground(UIUtil.getLabelDisabledForeground());
replacePanelLabel(toolWindow, label);
}

protected void presentClickableLabel(ToolWindow toolWindow, List<LabelInput> labels) {
final JPanel panel = new JPanel(new GridLayout(0, 1));

for (LabelInput input : labels) {
if (input.listener == null) {
final JLabel descriptionLabel = new JLabel("<html>" + input.text + "</html>");
descriptionLabel.setBorder(JBUI.Borders.empty(5));
descriptionLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(descriptionLabel, BorderLayout.NORTH);
}
else {
final LinkLabel<String> linkLabel = new LinkLabel<>("<html>" + input.text + "</html>", null);
linkLabel.setBorder(JBUI.Borders.empty(5));
linkLabel.setListener(input.listener, null);
linkLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(linkLabel, BorderLayout.SOUTH);
}
}

final JPanel center = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.CENTER));
center.add(panel);
replacePanelLabel(toolWindow, center);
viewUtils.presentClickableLabel(toolWindow, inputs);
}

protected void presentOpenDevToolsOptionWithMessage(FlutterApp app,
ToolWindow toolWindow,
String message,
DevToolsIdeFeature ideFeature) {
final List<LabelInput> inputs = new ArrayList<>();
inputs.add(new LabelInput(message));
inputs.add(openDevToolsLabel(app, toolWindow, ideFeature));
presentClickableLabel(toolWindow, inputs);
viewUtils.presentClickableLabel(toolWindow, inputs);
}

private void replacePanelLabel(ToolWindow toolWindow, JComponent label) {
Expand Down
68 changes: 68 additions & 0 deletions flutter-idea/src/io/flutter/view/ViewUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2025 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
package io.flutter.view;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import io.flutter.utils.LabelInput;

import javax.swing.*;
import java.awt.*;
import java.util.List;

public class ViewUtils {
public void presentLabel(ToolWindow toolWindow, String text) {
final JBLabel label = new JBLabel(text, SwingConstants.CENTER);
label.setForeground(UIUtil.getLabelDisabledForeground());
replacePanelLabel(toolWindow, label);
}

public void presentClickableLabel(ToolWindow toolWindow, List<LabelInput> labels) {
final JPanel panel = new JPanel(new GridLayout(0, 1));

for (LabelInput input : labels) {
if (input.listener == null) {
final JLabel descriptionLabel = new JLabel("<html>" + input.text + "</html>");
descriptionLabel.setBorder(JBUI.Borders.empty(5));
descriptionLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(descriptionLabel, BorderLayout.NORTH);
}
else {
final LinkLabel<String> linkLabel = new LinkLabel<>("<html>" + input.text + "</html>", null);
linkLabel.setBorder(JBUI.Borders.empty(5));
linkLabel.setListener(input.listener, null);
linkLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(linkLabel, BorderLayout.SOUTH);
}
}

final JPanel center = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.CENTER));
center.add(panel);
replacePanelLabel(toolWindow, center);
}

private void replacePanelLabel(ToolWindow toolWindow, JComponent label) {
ApplicationManager.getApplication().invokeLater(() -> {
final ContentManager contentManager = toolWindow.getContentManager();
if (contentManager.isDisposed()) {
return;
}

final JPanel panel = new JPanel(new BorderLayout());
panel.add(label, BorderLayout.CENTER);
final Content content = contentManager.getFactory().createContent(panel, null, false);
contentManager.removeAllContents(true);
contentManager.addContent(content);
});
}
}
Loading