Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -4096,7 +4096,8 @@ void updateBkImages(boolean colorChanged) {
int tabHeight = getTabHeight();
int height = this.getSize().y;
boolean wrapped = onBottom ? bounds.y + bounds.height < height - tabHeight : bounds.y > tabHeight;
if (wrapped || gradientColors == null) {
// only a custom renderer can paint something else here
if (useDefaultRenderer && (gradientColors == null || wrapped)) {
bkImageBounds[i]=null;
control.setBackgroundImage(null);
control.setBackground(getBackground());
Expand All @@ -4110,11 +4111,21 @@ void updateBkImages(boolean colorChanged) {
bounds.y = -1;
}
bounds.x = 0;
if (bounds.height <= 0) {
// drop the cache, so a skipped color change is not lost
bkImageBounds[i] = null;
continue;
}
// do not redraw when only translated:
if (colorChanged || !bounds.equals(bkImageBounds[i])) {
bkImageBounds[i] = bounds;
if (controlBkImages[i] != null) controlBkImages[i].dispose();
controlBkImages[i] = new Image(control.getDisplay(), (gc, imageWidth, imageHeight) -> renderer.draw(CTabFolderRenderer.PART_BACKGROUND, 0, bounds, gc), bounds.width, bounds.height);
controlBkImages[i] = new Image(control.getDisplay(), (gc, imageWidth, imageHeight) -> {
// pixels a renderer leaves untouched would stay blank in an image
gc.setBackground(getBackground());
gc.fillRectangle(0, 0, imageWidth, imageHeight);
renderer.draw(CTabFolderRenderer.PART_BACKGROUND, 0, bounds, gc);
}, bounds.width, bounds.height);
control.setBackground(null);
control.setBackgroundImage(controlBkImages[i]);
}
Expand Down
2 changes: 2 additions & 0 deletions examples/org.eclipse.swt.snippets/Snippets.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ To contribute a new snippet, [create a snippet contribution as a pull request](h
- [create a non-rectangular button](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet294.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet294.png "Preview for Snippet 294")
- [create buttons with wrapped titles](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet345.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet345.png "Preview for Snippet 345")


### **Canvas**
- [paint a circle in a canvas](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet245.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet245.png "Preview for Snippet 245")
- [scroll an image (flicker free, no double buffering)](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet48.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet48.png "Preview for Snippet 48")
Expand Down Expand Up @@ -119,6 +120,7 @@ To contribute a new snippet, [create a snippet contribution as a pull request](h
- [min and max buttons, close button and image only on selected tab](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet165.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet165.png "Preview for Snippet 165")
- [demonstration of a multi line CTabFolder](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet371.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet371.png "Preview for Snippet 371")
- [dirty indicator using bullet dot on close button with theme switching](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet394.java)
- [background of a top right control that wraps below the tab row](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet396.java)

### **Cursor**
- [set the hand cursor into a control](https://github.com/eclipse-platform/eclipse.platform.swt/tree/master/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet44.java) – [(preview)](https://github.com/eclipse-platform/eclipse.platform.swt/blob/master/examples/org.eclipse.swt.snippets/previews/Snippet44.png "Preview for Snippet 44")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*******************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.swt.snippets;

/*
* CTabFolder example: background of a top right control that wrapped below the
* tab row. Widen the shell until the tool bar fits next to the tabs.
*
* For a list of all SWT example snippets see
* http://www.eclipse.org/swt/snippets/
*/
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class Snippet396 {
static final Color TAB_ROW = new Color(0x21, 0x22, 0x2C);
static final Color BODY = new Color(0x28, 0x2A, 0x36);

/** Paints tab row and body in two different colors. */
static class TwoToneRenderer extends CTabFolderRenderer {
TwoToneRenderer(CTabFolder parent) {
super(parent);
}

@Override
protected void draw(int part, int state, Rectangle bounds, GC gc) {
if (part == PART_BACKGROUND) {
int split = bounds.y + parent.getTabHeight();
gc.setBackground(TAB_ROW);
gc.fillRectangle(bounds.x, bounds.y, bounds.width, split - bounds.y);
gc.setBackground(BODY);
gc.fillRectangle(bounds.x, split, bounds.width, bounds.y + bounds.height - split);
return;
}
super.draw(part, state, bounds, gc);
if (part == PART_HEADER) {
int split = bounds.y + parent.getTabHeight() + 3;
gc.setBackground(BODY);
gc.fillRectangle(bounds.x, split, bounds.width, bounds.y + bounds.height - split);
}
}
}

public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("Wrapped top right control");
shell.setLayout(new FillLayout());

CTabFolder folder = new CTabFolder(shell, SWT.NONE);
folder.setRenderer(new TwoToneRenderer(folder));
folder.setBackground(TAB_ROW);
folder.setForeground(display.getSystemColor(SWT.COLOR_WHITE));

for (String name : new String[] { "Variables", "Breakpoints", "Expressions" }) {
CTabItem item = new CTabItem(folder, SWT.NONE);
item.setText(name);
Composite content = new Composite(folder, SWT.NONE);
content.setBackground(BODY);
item.setControl(content);
}
folder.setSelection(0);

Composite topRight = new Composite(folder, SWT.NONE);
topRight.setLayout(new FillLayout());
ToolBar toolBar = new ToolBar(topRight, SWT.FLAT);
for (String text : new String[] { "Collapse", "Expand", "Filter", "Menu" }) {
new ToolItem(toolBar, SWT.PUSH).setText(text);
}
folder.setTopRight(topRight, SWT.RIGHT | SWT.WRAP);

shell.setSize(360, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,17 @@
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Listener;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabFolderRenderer;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.DPIUtil;
import org.eclipse.swt.layout.FillLayout;
Expand Down Expand Up @@ -1166,6 +1170,141 @@ public void test_moveItem_errorCases() {
"out-of-range to index must be rejected");
}

/* Paints tab row and body in two colors, neither of them the folder background. */
private static final RGB TAB_ROW_COLOR = new RGB(255, 0, 0);
private static final RGB BODY_COLOR = new RGB(0, 255, 0);
private static final RGB FOLDER_BACKGROUND = new RGB(0, 0, 255);

private static final class TwoToneRenderer extends CTabFolderRenderer {
TwoToneRenderer(CTabFolder parent) {
super(parent);
}

@Override
protected void draw(int part, int state, Rectangle bounds, GC gc) {
if (part == PART_BACKGROUND) {
int split = bounds.y + parent.getTabHeight();
gc.setBackground(new Color(TAB_ROW_COLOR));
gc.fillRectangle(bounds.x, bounds.y, bounds.width, split - bounds.y);
gc.setBackground(new Color(BODY_COLOR));
gc.fillRectangle(bounds.x, split, bounds.width, bounds.y + bounds.height - split);
return;
}
super.draw(part, state, bounds, gc);
}
}

/** A custom renderer that leaves PART_BACKGROUND to the built-in implementation. */
private static final class PlainSubclassRenderer extends CTabFolderRenderer {
PlainSubclassRenderer(CTabFolder parent) {
super(parent);
}
}

/** The color a control shows: its background image if it has one, else its background. */
private static RGB effectiveBackground(Control control) {
Image image = control.getBackgroundImage();
if (image != null) {
ImageData data = image.getImageData();
return data.palette.getRGB(data.getPixel(0, 0));
}
return control.getBackground().getRGB();
}

private Composite createFolderWithTopRightToolBar(int shellWidth, boolean withGradient) {
return createFolderWithTopRightToolBar(shellWidth, withGradient, true);
}

private Composite createFolderWithTopRightToolBar(int shellWidth, boolean withGradient, boolean twoTone) {
makeCleanEnvironment();
shell.setLayout(new FillLayout());
ctabFolder.setRenderer(twoTone ? new TwoToneRenderer(ctabFolder) : new PlainSubclassRenderer(ctabFolder));
ctabFolder.setBackground(new Color(FOLDER_BACKGROUND));
if (withGradient) {
ctabFolder.setBackground(new Color[] { new Color(FOLDER_BACKGROUND), new Color(FOLDER_BACKGROUND) },
new int[] { 100 });
}
for (int i = 0; i < 3; i++) {
CTabItem item = new CTabItem(ctabFolder, SWT.NONE);
item.setText("A rather long tab title " + i);
item.setControl(new Composite(ctabFolder, SWT.NONE));
}
ctabFolder.setSelection(0);

// the IDE wraps the tool bar in a Composite, which is what carries the background
Composite topRight = new Composite(ctabFolder, SWT.NONE);
topRight.setLayout(new FillLayout());
ToolBar toolBar = new ToolBar(topRight, SWT.FLAT);
for (int i = 0; i < 6; i++) {
new ToolItem(toolBar, SWT.PUSH).setText("Item " + i);
}
ctabFolder.setTopRight(topRight, SWT.RIGHT | SWT.WRAP);

shell.setSize(shellWidth, 300);
shell.open();
SwtTestUtil.processEvents();
ctabFolder.layout(true, true);
SwtTestUtil.processEvents();
return topRight;
}

@Test
public void test_topRightControl_wrappedBelowTabRow_matchesBody() {
Composite toolBar = createFolderWithTopRightToolBar(240, false);
assertTrue(toolBar.getBounds().y > ctabFolder.getTabHeight(),
"tool bar did not wrap below the tab row, bounds " + toolBar.getBounds());
assertEquals(BODY_COLOR, effectiveBackground(toolBar),
"a wrapped control sits on the body and has to match what the renderer paints there");
}

@Test
public void test_topRightControl_inTabRow_matchesTabRow() {
Composite toolBar = createFolderWithTopRightToolBar(900, false);
assertFalse(toolBar.getBounds().y > ctabFolder.getTabHeight(),
"tool bar unexpectedly wrapped, bounds " + toolBar.getBounds());
assertEquals(TAB_ROW_COLOR, effectiveBackground(toolBar),
"a control in the tab row has to match what the renderer paints there");
}

@Test
public void test_topRightControl_rendererKeepingDefaultBackground_staysOnTheFolderBackground() {
Composite toolBar = createFolderWithTopRightToolBar(240, false, false);
assertEquals(FOLDER_BACKGROUND, effectiveBackground(toolBar),
"a renderer that does not paint PART_BACKGROUND has to leave the folder background");
}

@Test
public void test_topRightControl_defaultRendererWithGradient_keepsFlatBackground() {
makeCleanEnvironment();
shell.setLayout(new FillLayout());
ctabFolder.setBackground(new Color(FOLDER_BACKGROUND));
ctabFolder.setBackground(new Color[] { new Color(TAB_ROW_COLOR), new Color(BODY_COLOR) },
new int[] { 100 });
for (int i = 0; i < 3; i++) {
CTabItem item = new CTabItem(ctabFolder, SWT.NONE);
item.setText("A rather long tab title " + i);
item.setControl(new Composite(ctabFolder, SWT.NONE));
}
ctabFolder.setSelection(0);
Composite topRight = new Composite(ctabFolder, SWT.NONE);
topRight.setLayout(new FillLayout());
ToolBar toolBar = new ToolBar(topRight, SWT.FLAT);
for (int i = 0; i < 6; i++) {
new ToolItem(toolBar, SWT.PUSH).setText("Item " + i);
}
ctabFolder.setTopRight(topRight, SWT.RIGHT | SWT.WRAP);
shell.setSize(240, 300);
shell.open();
SwtTestUtil.processEvents();
ctabFolder.layout(true, true);
SwtTestUtil.processEvents();

assertTrue(topRight.getBounds().y > ctabFolder.getTabHeight(),
"tool bar did not wrap below the tab row, bounds " + topRight.getBounds());
assertNull(topRight.getBackgroundImage(),
"the built-in renderer paints the body flat, a wrapped control needs no image");
}

/** Layout with a preferred size the test can change at will. */
private static final class FixedSizeLayout extends Layout {
int width;
Expand Down
Loading