diff --git a/CHANGELOG.md b/CHANGELOG.md
index edc1055a00..188490a4ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
- Resolved a "Slow operations are prohibited on EDT" exception on Flutter Project creation (#8446, #8447, #8448)
- Made dev release daily instead of weekly
- Set the device selector component to opaque during its creation to avoid an unexpected background color (#8471)
+- Refactored `DeviceSelectorAction` and add rich icons to different platform devices (#8475)
## 87.1.0
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index 00891ec8a2..5abb736dd2 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -57,7 +57,7 @@
+ icon="FlutterIcons.Mobile"/>
+ icon="FlutterIcons.Mobile"/>
diff --git a/resources/icons/android.svg b/resources/icons/android.svg
new file mode 100644
index 0000000000..2ac939dfb6
--- /dev/null
+++ b/resources/icons/android.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/desktop.svg b/resources/icons/desktop.svg
new file mode 100644
index 0000000000..bbf3b5c3bc
--- /dev/null
+++ b/resources/icons/desktop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/ios.svg b/resources/icons/ios.svg
new file mode 100644
index 0000000000..8fd292c7f4
--- /dev/null
+++ b/resources/icons/ios.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/mobile.svg b/resources/icons/mobile.svg
new file mode 100644
index 0000000000..98fef2e48d
--- /dev/null
+++ b/resources/icons/mobile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/phone.png b/resources/icons/phone.png
deleted file mode 100644
index d5e0735a15..0000000000
Binary files a/resources/icons/phone.png and /dev/null differ
diff --git a/resources/icons/phone@2x.png b/resources/icons/phone@2x.png
deleted file mode 100644
index 51ebbf890f..0000000000
Binary files a/resources/icons/phone@2x.png and /dev/null differ
diff --git a/resources/icons/web.svg b/resources/icons/web.svg
new file mode 100644
index 0000000000..62a823c694
--- /dev/null
+++ b/resources/icons/web.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/FlutterIcons.java b/src/icons/FlutterIcons.java
index 6fe1bc6e91..9ee638bb9d 100644
--- a/src/icons/FlutterIcons.java
+++ b/src/icons/FlutterIcons.java
@@ -17,9 +17,14 @@ private static Icon load(String path) {
public static final Icon Flutter = load("/icons/flutter.png");
public static final Icon Flutter_2x = load("/icons/flutter@2x.png");
public static final Icon Flutter_test = load("/icons/flutter_test.png");
- public static final Icon Phone = load("/icons/phone.png");
public static final Icon RefreshItems = load("/icons/refresh_items.png");
+ public static final Icon Android = load("/icons/android.svg");
+ public static final Icon IOS = load("/icons/ios.svg");
+ public static final Icon Mobile = load("/icons/mobile.svg");
+ public static final Icon Desktop = load("/icons/desktop.svg");
+ public static final Icon Web = load("/icons/web.svg");
+
public static final Icon Dart_16 = load("/icons/dart_16.svg");
public static final Icon HotReload = load("/icons/hot-reload.png");
diff --git a/src/io/flutter/actions/DeviceSelectorAction.java b/src/io/flutter/actions/DeviceSelectorAction.java
index 771d041c6b..c2c23728b9 100644
--- a/src/io/flutter/actions/DeviceSelectorAction.java
+++ b/src/io/flutter/actions/DeviceSelectorAction.java
@@ -5,9 +5,11 @@
*/
package io.flutter.actions;
+import com.intellij.icons.AllIcons;
import com.intellij.ide.ActivityTracker;
+import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.*;
-import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
+import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
@@ -15,11 +17,15 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
-import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
-import com.intellij.ui.JBColor;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.scale.JBUIScale;
+import com.intellij.util.IconUtil;
import com.intellij.util.ModalityUiUtil;
+import com.intellij.util.ui.JBUI;
import icons.FlutterIcons;
import io.flutter.FlutterBundle;
import io.flutter.run.FlutterDevice;
@@ -30,17 +36,25 @@
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
-import java.awt.Component;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.util.*;
+import java.util.List;
+
+public class DeviceSelectorAction extends AnAction implements CustomComponentAction, DumbAware {
+ private static final Key CUSTOM_COMPONENT_KEY = Key.create("customComponent");
+ private static final Key ICON_LABEL_KEY = Key.create("iconLabel");
+ private static final Key TEXT_LABEL_KEY = Key.create("textLabel");
+ private static final Key ARROW_LABEL_KEY = Key.create("arrowLabel");
-public class DeviceSelectorAction extends ComboBoxAction implements DumbAware {
private final List actions = new ArrayList<>();
private final List knownProjects = Collections.synchronizedList(new ArrayList<>());
private @Nullable SelectDeviceAction selectedDeviceAction;
DeviceSelectorAction() {
- setSmallVariant(true);
+ super();
}
public @NotNull ActionUpdateThread getActionUpdateThread() {
@@ -48,61 +62,187 @@ public class DeviceSelectorAction extends ComboBoxAction implements DumbAware {
}
@Override
- public @NotNull JComponent createCustomComponent(@NotNull Presentation presentation, @NotNull String place) {
- final JComponent component = super.createCustomComponent(presentation, place);
- // Set component to be transparent to match other toolbar actions
- component.setOpaque(false);
- // Update child components.
- updateComponentChildrenStyles(component);
- return component;
- }
-
- private void updateComponentChildrenStyles(@NotNull JComponent parent) {
- final @Nullable Component[] children = parent.getComponents();
- if (children == null) {
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final Project project = e.getProject();
+ if (!isSelectorVisible(project)) {
return;
}
- for (Component child : children) {
- if (child instanceof JComponent jComponent) {
- jComponent.setOpaque(false);
+ final DefaultActionGroup group = new DefaultActionGroup();
+ group.addAll(actions);
+
+ final DataContext dataContext = e.getDataContext();
+ final JBPopupFactory factory = Objects.requireNonNull(JBPopupFactory.getInstance());
+ final ListPopup popup =
+ factory.createActionGroupPopup(null, group, dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
+
+ final Component component = e.getData(Objects.requireNonNull(PlatformCoreDataKeys.CONTEXT_COMPONENT));
+ if (component != null) {
+ popup.showUnderneathOf(component);
+ }
+ else {
+ popup.showInBestPositionFor(dataContext);
+ }
+ }
+
+ @Override
+ public @NotNull JComponent createCustomComponent(@NotNull Presentation presentation, @NotNull String place) {
+ final JBLabel iconLabel = new JBLabel(FlutterIcons.Mobile);
+ final JBLabel textLabel = new JBLabel();
+ final JBLabel arrowLabel = new JBLabel(IconUtil.scale(AllIcons.General.ChevronDown, null, 1.2f));
+
+ // Create a wrapper button for hover effects
+ final JButton button = new JButton() {
+ @Override
+ protected void paintComponent(@NotNull Graphics g) {
+ if (getModel() instanceof ButtonModel m && m.isRollover()) {
+ final @NotNull Graphics2D g2 = (Graphics2D)Objects.requireNonNull(g.create());
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.setColor(JBUI.CurrentTheme.ActionButton.hoverBackground());
+ final int arc = JBUIScale.scale(JBUI.getInt("MainToolbar.Button.arc", 12));
+ g2.fillRoundRect(0, 0, getWidth(), getHeight(), arc, arc);
+ g2.dispose();
+ }
+ super.paintComponent(g);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ final @Nullable JBLabel iconLabel = (JBLabel)getClientProperty(ICON_LABEL_KEY);
+ final @Nullable JBLabel textLabel = (JBLabel)getClientProperty(TEXT_LABEL_KEY);
+ final @Nullable JBLabel arrowLabel = (JBLabel)getClientProperty(ARROW_LABEL_KEY);
+
+ int width = 0;
+ int height = JBUI.scale(22);
+
+ if (iconLabel instanceof JBLabel label && label.getIcon() instanceof Icon icon) {
+ width += icon.getIconWidth();
+ height = Math.max(height, icon.getIconHeight());
+ }
+
+ if (textLabel instanceof JBLabel label && label.getText() instanceof String text && !text.isEmpty()) {
+ final FontMetrics fm = label.getFontMetrics(label.getFont());
+ width += Objects.requireNonNull(fm).stringWidth(text);
+ height = Math.max(height, fm.getHeight());
+ }
- if (child instanceof JButton jButton) {
- jButton.setBorderPainted(false);
- jButton.setRolloverEnabled(true);
- // Make sure the button uses correct background & foreground.
- jButton.setBackground(JBColor.background());
- jButton.setForeground(JBColor.foreground());
+ if (arrowLabel instanceof JBLabel label && label.getIcon() instanceof Icon icon) {
+ width += icon.getIconWidth();
+ height = Math.max(height, icon.getIconHeight());
}
- updateComponentChildrenStyles(jComponent);
+ width += JBUI.scale(24);
+ height += JBUI.scale(8);
+
+ return new Dimension(width, height);
}
+ };
+
+ button.setLayout(new BorderLayout());
+ button.setBorder(JBUI.Borders.empty(4, 8));
+
+ final JPanel contentPanel = getContentPanel();
+
+ final JBLabel[] labels = {iconLabel, textLabel, arrowLabel};
+ for (JBLabel label : labels) {
+ Objects.requireNonNull(label).addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(@NotNull MouseEvent e) {
+ button.dispatchEvent(SwingUtilities.convertMouseEvent(label, e, button));
+ }
+
+ @Override
+ public void mousePressed(@NotNull MouseEvent e) {
+ button.dispatchEvent(SwingUtilities.convertMouseEvent(label, e, button));
+ }
+
+ @Override
+ public void mouseReleased(@NotNull MouseEvent e) {
+ button.dispatchEvent(SwingUtilities.convertMouseEvent(label, e, button));
+ }
+
+ @Override
+ public void mouseEntered(@NotNull MouseEvent e) {
+ button.dispatchEvent(SwingUtilities.convertMouseEvent(label, e, button));
+ }
+
+ @Override
+ public void mouseExited(@NotNull MouseEvent e) {
+ button.dispatchEvent(SwingUtilities.convertMouseEvent(label, e, button));
+ }
+ });
}
+
+ contentPanel.add(iconLabel, BorderLayout.WEST);
+ contentPanel.add(textLabel, BorderLayout.CENTER);
+ contentPanel.add(arrowLabel, BorderLayout.EAST);
+
+ button.add(contentPanel, BorderLayout.CENTER);
+ button.setOpaque(false);
+ button.setContentAreaFilled(false);
+ button.setBorderPainted(false);
+ button.setFocusPainted(false);
+ button.setRolloverEnabled(true);
+
+ // Store references for updating
+ button.putClientProperty(ICON_LABEL_KEY, iconLabel);
+ button.putClientProperty(TEXT_LABEL_KEY, textLabel);
+ button.putClientProperty(ARROW_LABEL_KEY, arrowLabel);
+ presentation.putClientProperty(CUSTOM_COMPONENT_KEY, button);
+
+ button.addActionListener(e -> {
+ final DataKey dataKey = Objects.requireNonNull(CommonDataKeys.PROJECT);
+ final DataManager dataManager = Objects.requireNonNull(DataManager.getInstance());
+ final DataContext dataContext = dataManager.getDataContext(button);
+ final Project project = dataKey.getData(dataContext);
+ if (isSelectorVisible(project)) {
+ final DefaultActionGroup group = new DefaultActionGroup();
+ group.addAll(actions);
+
+ final JBPopupFactory factory = Objects.requireNonNull(JBPopupFactory.getInstance());
+ final ListPopup popup =
+ factory.createActionGroupPopup(null, group, dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
+ popup.showUnderneathOf(button);
+ }
+ });
+
+ return button;
}
- @Override
- protected @NotNull DefaultActionGroup createPopupActionGroup(@NotNull JComponent button, @NotNull DataContext dataContext) {
- final DefaultActionGroup group = new DefaultActionGroup();
- group.addAll(actions);
- return group;
+ private void showPopup(@NotNull AnActionEvent e) {
}
- @Override
- protected boolean shouldShowDisabledActions() {
- return true;
+ private @NotNull JPanel getContentPanel() {
+ final JPanel contentPanel = new JPanel(new BorderLayout(4, 0)) {
+ @Override
+ protected void processMouseEvent(@NotNull MouseEvent e) {
+ final Container parent = getParent();
+ if (parent != null) {
+ parent.dispatchEvent(SwingUtilities.convertMouseEvent(this, e, getParent()));
+ }
+ }
+
+ @Override
+ protected void processMouseMotionEvent(@NotNull MouseEvent e) {
+ final Container parent = getParent();
+ if (parent != null) {
+ parent.dispatchEvent(SwingUtilities.convertMouseEvent(this, e, getParent()));
+ }
+ }
+ };
+ contentPanel.setOpaque(false);
+ return contentPanel;
}
@Override
public void update(@NotNull AnActionEvent e) {
- // Only show device menu when the device daemon process is running.
+ // Only show the device menu when the device daemon process is running.
final Project project = e.getProject();
if (!isSelectorVisible(project)) {
e.getPresentation().setVisible(false);
return;
}
- super.update(e);
-
final Presentation presentation = e.getPresentation();
if (!knownProjects.contains(project)) {
knownProjects.add(project);
@@ -118,7 +258,7 @@ public void projectClosed(@NotNull Project closedProject) {
Runnable deviceListener = () -> queueUpdate(project, e.getPresentation());
DeviceService.getInstance(project).addListener(deviceListener);
- // Listen for android device changes, and rebuild the menu if necessary.
+ // Listen for android device changes and rebuild the menu if necessary.
Runnable emulatorListener = () -> queueUpdate(project, e.getPresentation());
AndroidEmulatorManager.getInstance(project).addListener(emulatorListener);
var projectManager = ProjectManager.getInstance();
@@ -134,34 +274,61 @@ public void projectClosing(@NotNull Project project) {
}
final DeviceService deviceService = DeviceService.getInstance(project);
-
final FlutterDevice selectedDevice = deviceService.getSelectedDevice();
final Collection devices = deviceService.getConnectedDevices();
+ final String text;
+ Icon icon = FlutterIcons.Mobile;
+
if (devices.isEmpty()) {
final boolean isLoading = deviceService.getStatus() == DeviceService.State.LOADING;
if (isLoading) {
- presentation.setText(FlutterBundle.message("devicelist.loading"));
+ text = FlutterBundle.message("devicelist.loading");
}
else {
- presentation.setText("");
+ text = "";
}
}
else if (selectedDevice == null) {
- presentation.setText("");
+ text = "";
}
- else if (selectedDeviceAction != null) {
- final Presentation template = selectedDeviceAction.getTemplatePresentation();
- presentation.setIcon(template.getIcon());
- presentation.setText(selectedDevice.presentationName());
+ else {
+ text = selectedDevice.presentationName();
+ icon = selectedDevice.getIcon();
presentation.setEnabled(true);
}
+
+ presentation.setText(text);
+ presentation.setIcon(icon);
+
+ // Update the custom component if it exists
+ final JButton customComponent = presentation.getClientProperty(CUSTOM_COMPONENT_KEY);
+ if (customComponent != null) {
+ final @Nullable JBLabel iconLabel = (JBLabel)customComponent.getClientProperty(ICON_LABEL_KEY);
+ final @Nullable JBLabel textLabel = (JBLabel)customComponent.getClientProperty(TEXT_LABEL_KEY);
+
+ if (iconLabel != null) {
+ iconLabel.setIcon(icon);
+ }
+ if (textLabel != null) {
+ textLabel.setText(text);
+ customComponent.invalidate();
+ Container parent = customComponent.getParent();
+ while (parent != null) {
+ parent.invalidate();
+ parent = parent.getParent();
+ }
+ customComponent.revalidate();
+ customComponent.repaint();
+ }
+ }
}
private void queueUpdate(@NotNull Project project, @NotNull Presentation presentation) {
ModalityUiUtil.invokeLaterIfNeeded(
ModalityState.defaultModalityState(),
- () -> update(project, presentation));
+ () -> update(project, presentation)
+ );
}
private void update(@NotNull Project project, @NotNull Presentation presentation) {
@@ -175,7 +342,7 @@ private void update(@NotNull Project project, @NotNull Presentation presentation
private static void updateVisibility(final Project project, final @NotNull Presentation presentation) {
final boolean visible = isSelectorVisible(project);
- final JComponent component = presentation.getClientProperty(new Key<>("customComponent"));
+ final JComponent component = presentation.getClientProperty(CUSTOM_COMPONENT_KEY);
if (component != null) {
component.setVisible(visible);
var parent = component.getParent();
@@ -194,7 +361,7 @@ private static boolean isSelectorVisible(@Nullable Project project) {
return deviceService.isRefreshInProgress() || deviceService.getStatus() != DeviceService.State.INACTIVE;
}
- private void updateActions(@NotNull Project project, Presentation presentation) {
+ private void updateActions(@NotNull Project project, @NotNull Presentation presentation) {
actions.clear();
final DeviceService deviceService = DeviceService.getInstance(project);
@@ -212,11 +379,7 @@ private void updateActions(@NotNull Project project, Presentation presentation)
if (Objects.equals(device, selectedDevice)) {
selectedDeviceAction = deviceAction;
-
- final Presentation template = deviceAction.getTemplatePresentation();
- //noinspection DataFlowIssue
- presentation.setIcon(template.getIcon());
- //presentation.setText(deviceAction.presentationName());
+ presentation.setIcon(device.getIcon());
presentation.setEnabled(true);
}
}
@@ -254,18 +417,12 @@ private void updateActions(@NotNull Project project, Presentation presentation)
}
}
- // Show the current device as selected when the combo box menu opens.
- @Override
- protected Condition getPreselectCondition() {
- return action -> action == selectedDeviceAction;
- }
private static class SelectDeviceAction extends AnAction {
- @NotNull
- private final FlutterDevice device;
+ @NotNull private final FlutterDevice device;
SelectDeviceAction(@NotNull FlutterDevice device, @NotNull Collection devices) {
- super(device.getUniqueName(devices), null, FlutterIcons.Phone);
+ super(device.getUniqueName(devices), null, device.getIcon());
this.device = device;
}
diff --git a/src/io/flutter/actions/OpenEmulatorAction.java b/src/io/flutter/actions/OpenEmulatorAction.java
index 87efb7ca28..afd21b354a 100644
--- a/src/io/flutter/actions/OpenEmulatorAction.java
+++ b/src/io/flutter/actions/OpenEmulatorAction.java
@@ -9,6 +9,7 @@
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
+import icons.FlutterIcons;
import io.flutter.android.AndroidEmulator;
import io.flutter.sdk.AndroidEmulatorManager;
import org.jetbrains.annotations.NotNull;
@@ -40,8 +41,7 @@ public static List getEmulatorActions(Project project) {
final @NotNull AndroidEmulator emulator;
public OpenEmulatorAction(@NotNull AndroidEmulator emulator) {
- super("Open Android Emulator: " + emulator.getName());
-
+ super("Open Android Emulator: " + emulator.getName(), null, FlutterIcons.Android);
this.emulator = emulator;
}
diff --git a/src/io/flutter/actions/OpenSimulatorAction.java b/src/io/flutter/actions/OpenSimulatorAction.java
index e2fe39a3d8..8cd7cc9bfb 100644
--- a/src/io/flutter/actions/OpenSimulatorAction.java
+++ b/src/io/flutter/actions/OpenSimulatorAction.java
@@ -9,6 +9,7 @@
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
+import icons.FlutterIcons;
import io.flutter.sdk.XcodeUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -17,8 +18,7 @@ public class OpenSimulatorAction extends AnAction {
final boolean enabled;
public OpenSimulatorAction(boolean enabled) {
- super("Open iOS Simulator");
-
+ super("Open iOS Simulator", null, FlutterIcons.IOS);
this.enabled = enabled;
}
diff --git a/src/io/flutter/devtools/AbstractDevToolsViewFactory.java b/src/io/flutter/devtools/AbstractDevToolsViewFactory.java
index 48c0e64124..eb9eee826c 100644
--- a/src/io/flutter/devtools/AbstractDevToolsViewFactory.java
+++ b/src/io/flutter/devtools/AbstractDevToolsViewFactory.java
@@ -11,6 +11,7 @@
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.openapi.wm.ex.ToolWindowManagerListener;
import com.intellij.util.messages.MessageBusConnection;
+import icons.FlutterIcons;
import io.flutter.FlutterUtils;
import io.flutter.actions.RefreshToolWindowAction;
import io.flutter.run.daemon.DevToolsInstance;
@@ -25,6 +26,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import javax.swing.Icon;
import java.util.List;
import java.util.Optional;
@@ -41,6 +43,11 @@ public abstract class AbstractDevToolsViewFactory implements ToolWindowFactory {
@NotNull
public abstract String getToolWindowTitle();
+ @Nullable
+ public Icon getToolWindowIcon() {
+ return null;
+ }
+
@NotNull
public abstract DevToolsUrl getDevToolsUrl(@NotNull Project project,
@NotNull FlutterSdkVersion flutterSdkVersion,
@@ -150,7 +157,7 @@ private void loadDevToolsInEmbeddedBrowser(@NotNull Project project,
FlutterUtils.embeddedBrowser(project))
.ifPresent(embeddedBrowser ->
{
- embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), devToolsUrl, System.out::println, warningMessage);
+ embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), getToolWindowIcon(), devToolsUrl, System.out::println, warningMessage);
devToolsLoadedInBrowser = true;
doAfterBrowserOpened(project, embeddedBrowser);
// The "refresh" action refreshes the embedded browser, not the panel.
diff --git a/src/io/flutter/run/FlutterDevice.java b/src/io/flutter/run/FlutterDevice.java
index 0d3375e487..4fee0d92de 100644
--- a/src/io/flutter/run/FlutterDevice.java
+++ b/src/io/flutter/run/FlutterDevice.java
@@ -5,10 +5,12 @@
*/
package io.flutter.run;
+import icons.FlutterIcons;
import io.flutter.sdk.XcodeUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import javax.swing.Icon;
import java.util.Collection;
import java.util.Objects;
@@ -85,6 +87,31 @@ public boolean isIOS() {
return myPlatform != null && (myPlatform.equals("ios") || myPlatform.startsWith("darwin"));
}
+ public boolean isMobile() {
+ return myCategory != null && myCategory.equals("mobile");
+ }
+
+ public boolean isDesktop() {
+ return myCategory != null && myCategory.equals("desktop");
+ }
+
+ public boolean isWeb() {
+ return myCategory != null && (myCategory.equals("web"));
+ }
+
+ public Icon getIcon() {
+ if (isDesktop()) {
+ return FlutterIcons.Desktop;
+ }
+ else if (isWeb()) {
+ return FlutterIcons.Web;
+ }
+ // Use the mobile icon for all other devices.
+ else {
+ return FlutterIcons.Mobile;
+ }
+ }
+
@Override
public boolean equals(Object other) {
//noinspection SimplifiableIfStatement
diff --git a/src/io/flutter/view/EmbeddedBrowser.java b/src/io/flutter/view/EmbeddedBrowser.java
index b7dbf78328..ad6d9c91b7 100644
--- a/src/io/flutter/view/EmbeddedBrowser.java
+++ b/src/io/flutter/view/EmbeddedBrowser.java
@@ -80,13 +80,15 @@ public void projectClosing(@NotNull Project project) {
public void openPanel(@NotNull ToolWindow toolWindow,
@NotNull String tabName,
+ @Nullable Icon tabIcon,
@NotNull DevToolsUrl devToolsUrl,
@NotNull Consumer onBrowserUnavailable) {
- openPanel(toolWindow, tabName, devToolsUrl, onBrowserUnavailable, null);
+ openPanel(toolWindow, tabName, tabIcon, devToolsUrl, onBrowserUnavailable, null);
}
public void openPanel(@NotNull ToolWindow toolWindow,
@NotNull String tabName,
+ @Nullable Icon tabIcon,
@NotNull DevToolsUrl devToolsUrl,
@NotNull Consumer onBrowserUnavailable,
@Nullable String warningMessage) {
@@ -146,9 +148,11 @@ public void openPanel(@NotNull ToolWindow toolWindow,
tab.content = tab.contentManager.getFactory().createContent(null, tabName, false);
tab.content.setComponent(component);
- tab.content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
- // TODO(helin24): Use differentiated icons for each tab and copy from devtools toolbar.
- tab.content.setIcon(FlutterIcons.Phone);
+
+ tab.content.putUserData(ToolWindow.SHOW_CONTENT_ICON, tabIcon != null);
+ if (tabIcon != null) {
+ tab.content.setIcon(tabIcon);
+ }
final JPanel panel = new JPanel(new BorderLayout());
diff --git a/src/io/flutter/view/InspectorView.java b/src/io/flutter/view/InspectorView.java
index 19baa2b4f1..4a84ace0b3 100644
--- a/src/io/flutter/view/InspectorView.java
+++ b/src/io/flutter/view/InspectorView.java
@@ -106,7 +106,7 @@ protected InspectorView(@NotNull Project project,
public void dispose() {
Disposer.dispose(this);
}
-
+
void initToolWindow(@NotNull ToolWindow window) {
if (window.isDisposed()) return;
@@ -125,6 +125,7 @@ private void addBrowserInspectorViewContent(@NotNull FlutterApp app,
final FlutterDevice device = app.device();
final List existingDevices = new ArrayList<>();
final String tabName = device.getUniqueName(existingDevices);
+ final Icon tabIcon = device.getIcon();
if (emptyContent != null) {
contentManager.removeContent(emptyContent, true);
@@ -160,7 +161,7 @@ private void addBrowserInspectorViewContent(@NotNull FlutterApp app,
Runnable task = () -> {
embeddedBrowserOptional().ifPresent(
embeddedBrowser -> OpenApiUtils.safeInvokeLater(() -> {
- embeddedBrowser.openPanel(toolWindow, tabName, devToolsUrl, (String error) -> {
+ embeddedBrowser.openPanel(toolWindow, tabName, tabIcon, devToolsUrl, (String error) -> {
// If the embedded browser doesn't work, offer a link to open in the regular browser.
final List inputs = Arrays.asList(
new LabelInput("The embedded browser failed to load. Error: " + error),