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),