1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Round the corners of the color picker (#8439)

The color picker's gradients and background checkers now follow the
style's corner radius, giving it a softer look.

This required changing the painting from being Mesh-based to being
texture-based

it's subtle with the default settings:

<img width="286" height="397" alt="Screenshot 2026-08-21 at 15 08 18"
src="https://github.com/user-attachments/assets/3a9d5289-71fb-4f34-8a20-1fe96891ba36"
/>


But you can [increase it](https://github.com/emilk/egui/pull/8445):


<img width="285" height="391" alt="Screenshot 2026-08-21 at 15 08 41"
src="https://github.com/user-attachments/assets/b0dfa7da-5b96-40e0-a80d-1da385d57a1a"
/>


### Before
for reference
<img width="286" height="392" alt="Screenshot 2026-08-21 at 19 08 27"
src="https://github.com/user-attachments/assets/7c754f36-795c-4381-9f9f-1fc8dd9a0264"
/>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Emil Ernerfeldt
2026-08-24 02:28:14 -07:00
committed by GitHub
parent a15a7e6611
commit 726b995608
24 changed files with 411 additions and 155 deletions

View File

@@ -98,9 +98,38 @@ fn vs_main(
@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
@group(1) @binding(1) var r_tex_sampler: sampler;
/// 1 if the texture sampler uses nearest filtering, 0 if linear.
/// Only read when `predictable_texture_filtering` is on.
@group(1) @binding(2) var<uniform> r_tex_nearest_filtering: u32;
/// Set in bit 0 of `r_tex_flags` if the sampler uses nearest filtering.
///
/// Must match `TEX_FLAG_NEAREST` in `renderer.rs`.
const TEX_FLAG_NEAREST: u32 = 1u;
/// Wrap modes, stored in bits 1+ of `r_tex_flags`.
///
/// Must match the `WRAP_MODE_*` constants in `renderer.rs`.
const WRAP_MODE_CLAMP_TO_EDGE: u32 = 0u;
const WRAP_MODE_REPEAT: u32 = 1u;
const WRAP_MODE_MIRRORED_REPEAT: u32 = 2u;
/// Texture flags, only read when `predictable_texture_filtering` is on.
///
/// Bit 0: `TEX_FLAG_NEAREST`.
/// Bits 1+: one of the `WRAP_MODE_*` constants.
@group(1) @binding(2) var<uniform> r_tex_flags: u32;
/// Map a texel coordinate to a valid texel according to the texture's wrap mode.
fn wrap_texel_coord(coord: vec2<i32>, texture_size: vec2<i32>) -> vec2<i32> {
let wrap_mode = r_tex_flags >> 1u;
if wrap_mode == WRAP_MODE_REPEAT {
return ((coord % texture_size) + texture_size) % texture_size;
} else if wrap_mode == WRAP_MODE_MIRRORED_REPEAT {
let period = 2 * texture_size;
let phase = ((coord % period) + period) % period;
return min(phase, period - vec2<i32>(1, 1) - phase);
} else {
// WRAP_MODE_CLAMP_TO_EDGE
return clamp(coord, vec2<i32>(0, 0), texture_size - vec2<i32>(1, 1));
}
}
fn sample_texture(in: VertexOutput) -> vec4<f32> {
if r_locals.predictable_texture_filtering == 0 {
@@ -109,11 +138,10 @@ fn sample_texture(in: VertexOutput) -> vec4<f32> {
} else {
let texture_size = vec2<i32>(textureDimensions(r_tex_color, 0));
let texture_size_f = vec2<f32>(texture_size);
let max_coord = texture_size - vec2<i32>(1, 1);
if r_tex_nearest_filtering == 1 {
if (r_tex_flags & TEX_FLAG_NEAREST) != 0u {
// Nearest filtering: load the texel under the sample position.
let texel = clamp(vec2<i32>(in.tex_coord * texture_size_f), vec2<i32>(0, 0), max_coord);
let texel = wrap_texel_coord(vec2<i32>(floor(in.tex_coord * texture_size_f)), texture_size);
return textureLoad(r_tex_color, texel, 0);
}
@@ -122,11 +150,10 @@ fn sample_texture(in: VertexOutput) -> vec4<f32> {
let pixel_fract = fract(pixel_coord);
let pixel_floor = vec2<i32>(floor(pixel_coord));
// Manual texture clamping
let p00 = clamp(pixel_floor + vec2<i32>(0, 0), vec2<i32>(0, 0), max_coord);
let p10 = clamp(pixel_floor + vec2<i32>(1, 0), vec2<i32>(0, 0), max_coord);
let p01 = clamp(pixel_floor + vec2<i32>(0, 1), vec2<i32>(0, 0), max_coord);
let p11 = clamp(pixel_floor + vec2<i32>(1, 1), vec2<i32>(0, 0), max_coord);
let p00 = wrap_texel_coord(pixel_floor + vec2<i32>(0, 0), texture_size);
let p10 = wrap_texel_coord(pixel_floor + vec2<i32>(1, 0), texture_size);
let p01 = wrap_texel_coord(pixel_floor + vec2<i32>(0, 1), texture_size);
let p11 = wrap_texel_coord(pixel_floor + vec2<i32>(1, 1), texture_size);
// Load at pixel centers
let tl = textureLoad(r_tex_color, p00, 0);

View File

@@ -245,11 +245,10 @@ pub struct Renderer {
uniform_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
/// Uniform buffers each holding a single `u32`:
/// 1 if the texture sampler uses nearest filtering, 0 otherwise.
/// Indexed by that flag value.
/// Uniform buffers each holding a single `u32` of texture flags
/// (see [`texture_flags`]), indexed by that flag value.
/// Read by the shader when `predictable_texture_filtering` is on.
nearest_filtering_flag_buffers: [wgpu::Buffer; 2],
texture_flag_buffers: [wgpu::Buffer; NUM_TEXTURE_FLAGS],
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
/// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided
@@ -367,9 +366,10 @@ impl Renderer {
})
};
let nearest_filtering_flag_buffers = [0_u32, 1_u32].map(|flag| {
let texture_flag_buffers = core::array::from_fn::<_, NUM_TEXTURE_FLAGS, _>(|flag| {
let flag = flag as u32;
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("egui_nearest_filtering_flag_{flag}")),
label: Some(&format!("egui_texture_flags_{flag}")),
contents: bytemuck::bytes_of(&flag),
usage: wgpu::BufferUsages::UNIFORM,
})
@@ -482,7 +482,7 @@ impl Renderer {
previous_uniform_buffer_content: UniformBuffer::zeroed(),
uniform_bind_group,
texture_bind_group_layout,
nearest_filtering_flag_buffers,
texture_flag_buffers,
textures: HashMap::default(),
next_user_texture_id: 0,
samplers: HashMap::default(),
@@ -736,6 +736,7 @@ impl Renderer {
let bind_group = bind_group.unwrap_or_else(|| {
let nearest =
image_delta.options.magnification == epaint::textures::TextureFilter::Nearest;
let wrap_mode = wrap_mode_flag(image_delta.options.wrap_mode);
let sampler = self
.samplers
.entry(image_delta.options)
@@ -756,7 +757,7 @@ impl Renderer {
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.nearest_filtering_flag_buffers[usize::from(nearest)]
resource: self.texture_flag_buffers[texture_flags(nearest, wrap_mode)]
.as_entire_binding(),
},
],
@@ -862,6 +863,7 @@ impl Renderer {
profiling::function_scope!();
let nearest = sampler_descriptor.mag_filter == wgpu::FilterMode::Nearest;
let wrap_mode = address_mode_wrap_flag(sampler_descriptor.address_mode_u);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: None,
..sampler_descriptor
@@ -881,7 +883,7 @@ impl Renderer {
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.nearest_filtering_flag_buffers[usize::from(nearest)]
resource: self.texture_flag_buffers[texture_flags(nearest, wrap_mode)]
.as_entire_binding(),
},
],
@@ -924,6 +926,7 @@ impl Renderer {
.expect("Tried to update a texture that has not been allocated yet.");
let nearest = sampler_descriptor.mag_filter == wgpu::FilterMode::Nearest;
let wrap_mode = address_mode_wrap_flag(sampler_descriptor.address_mode_u);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
compare: None,
..sampler_descriptor
@@ -943,7 +946,7 @@ impl Renderer {
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.nearest_filtering_flag_buffers[usize::from(nearest)]
resource: self.texture_flag_buffers[texture_flags(nearest, wrap_mode)]
.as_entire_binding(),
},
],
@@ -1124,6 +1127,49 @@ impl Renderer {
}
}
/// Set in bit 0 of the texture flags if the sampler uses nearest filtering.
///
/// Must match `TEX_FLAG_NEAREST` in `egui.wgsl`.
const TEX_FLAG_NEAREST: u32 = 1;
/// Wrap modes, stored in bits 1+ of the texture flags.
///
/// Must match the `WRAP_MODE_*` constants in `egui.wgsl`.
const WRAP_MODE_CLAMP_TO_EDGE: u32 = 0;
const WRAP_MODE_REPEAT: u32 = 1;
const WRAP_MODE_MIRRORED_REPEAT: u32 = 2;
/// Number of distinct values [`texture_flags`] can return.
const NUM_TEXTURE_FLAGS: usize = 6;
fn wrap_mode_flag(wrap_mode: epaint::textures::TextureWrapMode) -> u32 {
match wrap_mode {
epaint::textures::TextureWrapMode::ClampToEdge => WRAP_MODE_CLAMP_TO_EDGE,
epaint::textures::TextureWrapMode::Repeat => WRAP_MODE_REPEAT,
epaint::textures::TextureWrapMode::MirroredRepeat => WRAP_MODE_MIRRORED_REPEAT,
}
}
fn address_mode_wrap_flag(address_mode: wgpu::AddressMode) -> u32 {
match address_mode {
wgpu::AddressMode::Repeat => WRAP_MODE_REPEAT,
wgpu::AddressMode::MirrorRepeat => WRAP_MODE_MIRRORED_REPEAT,
wgpu::AddressMode::ClampToEdge | wgpu::AddressMode::ClampToBorder => {
WRAP_MODE_CLAMP_TO_EDGE
}
}
}
/// Index into [`Renderer::texture_flag_buffers`]: the texture flags
/// read by the shader when `predictable_texture_filtering` is on.
///
/// Bit 0: [`TEX_FLAG_NEAREST`].
/// Bits 1+: one of the `WRAP_MODE_*` constants.
fn texture_flags(nearest: bool, wrap_mode: u32) -> usize {
let nearest = if nearest { TEX_FLAG_NEAREST } else { 0 };
(nearest | (wrap_mode << 1)) as usize
}
fn create_sampler(
options: epaint::textures::TextureOptions,
device: &wgpu::Device,