Skip to content

fix: keep IME preedit text visible on transparent backgrounds#381

Merged
Decodetalkers merged 1 commit into
waycrate:masterfrom
krteke:ime_preedit
May 22, 2026
Merged

fix: keep IME preedit text visible on transparent backgrounds#381
Decodetalkers merged 1 commit into
waycrate:masterfrom
krteke:ime_preedit

Conversation

@krteke
Copy link
Copy Markdown
Contributor

@krteke krteke commented May 21, 2026

This changes IME preedit rendering so that the preedit text always uses the application text color.

Previously, the selected preedit span used the window background color as its text color, while the selection background used the text color. This inverse-color approach breaks when the layer-shell background is transparent: selected preedit text becomes transparent and invisible.

The new behavior keeps the preedit background transparent when the window background is transparent, renders preedit text with text_color, and uses a subtle translucent text_color fill for the selected preedit range.

This makes IME preedit readable in transparent layer-shell windows while preserving visible selection feedback.

test code

use iced::widget::{column, container, text, text_input};
use iced::{Background, Color, Element, Fill, Task, Theme};
use iced_layershell::application;
use iced_layershell::reexport::{Anchor, KeyboardInteractivity, Layer};
use iced_layershell::settings::{
    LayerShellSettings, Settings as LayerShellApplicationSettings, StartMode,
};
use iced_layershell::to_layer_message;

fn main() -> iced_layershell::Result {
    application(
        || (App::default(), Task::none()),
        "iced-ime-transparent-layer",
        App::update,
        App::view,
    )
    .theme(App::theme)
    .style(App::style)
    .settings(layer_settings())
    .run()
}

#[derive(Default)]
struct App {
    value: String,
}

#[to_layer_message]
#[derive(Debug, Clone)]
enum Message {
    Changed(String),
}

impl App {
    fn update(&mut self, message: Message) -> Task<Message> {
        if let Message::Changed(value) = message {
            self.value = value;
        }

        Task::none()
    }

    fn view(&self) -> Element<'_, Message> {
        let input = text_input("Type with a Chinese IME...", &self.value)
            .on_input(Message::Changed)
            .size(34)
            .padding(12)
            .style(|theme: &Theme, status| {
                let mut style = iced::widget::text_input::default(theme, status);
                style.background = Background::Color(Color::from_rgb8(0x1d, 0x21, 0x29));
                style.value = Color::WHITE;
                style.placeholder = Color::from_rgb8(0x9a, 0xa3, 0xb2);
                style
            });

        container(
            column![
                text("IME preedit transparent layer-shell").size(28),
                input,
                text("Use fcitx5 rime and type: ni hao"),
            ]
            .spacing(16),
        )
        .width(Fill)
        .height(Fill)
        .padding(32)
        .style(|_| {
            iced::widget::container::Style::default()
                .background(Background::Color(Color::from_rgba8(0x20, 0x24, 0x2c, 0.92)))
                .color(Color::WHITE)
        })
        .into()
    }

    fn theme(&self) -> Theme {
        Theme::Dark
    }

    fn style(&self, _theme: &Theme) -> iced::theme::Style {
        iced::theme::Style {
            background_color: Color::TRANSPARENT,
            text_color: Color::WHITE,
        }
    }
}

fn layer_settings() -> LayerShellApplicationSettings {
    LayerShellApplicationSettings {
        id: Some("iced-ime-transparent-layer".to_owned()),
        antialiasing: true,
        layer_settings: LayerShellSettings {
            anchor: Anchor::Top | Anchor::Left | Anchor::Right,
            layer: Layer::Overlay,
            exclusive_zone: 0,
            size: Some((760, 320)),
            margin: (0, 0, 0, 0),
            keyboard_interactivity: KeyboardInteractivity::Exclusive,
            start_mode: StartMode::Active,
            events_transparent: false,
        },
        ..LayerShellApplicationSettings::default()
    }
}

before
foamshot-2026-05-21-23-25-26
now
foamshot-2026-05-21-23-24-55

I think the current approach is more reasonable.

IME preedit selection used the window background color as the
selected text color and the text color as the selection background.

This inverse-color approach breaks for layer-shell windows using a
transparent background, because selected preedit text becomes
transparent and invisible.

Render preedit text with the normal text color and use a translucent
selection background instead, so IME composition remains readable on
transparent surfaces.
@Decodetalkers
Copy link
Copy Markdown
Collaborator

can we take this as reference?

https://github.com/iced-rs/iced/blob/d4f568a4b3589bc73c41259804dc1fa3d785161a/winit/src/window.rs#L341-L456

I think we may still need to keep the background color, because it is also did in this way in iced

@krteke
Copy link
Copy Markdown
Contributor Author

krteke commented May 22, 2026

can we take this as reference?

https://github.com/iced-rs/iced/blob/d4f568a4b3589bc73c41259804dc1fa3d785161a/winit/src/window.rs#L341-L456

I think we may still need to keep the background color, because it is also did in this way in iced

Thanks, yes, I agree iced_layershell should probably follow iced upstream here.

After checking iced-rs/iced#2819, it looks like this behavior is intentional upstream: iced forces the preedit background alpha to 1.0 to avoid invisible IME preedit text when the application background is transparent.

However, this also means there is currently no way to have a transparent layer-shell window while keeping the IME preedit overlay transparent/readable, because preedit colors are derived from the application-level background/text colors instead of a dedicated preedit style.

It looks like I should open an Issue for iced first.

@Decodetalkers
Copy link
Copy Markdown
Collaborator

I see, you have already tried the upstream behavior, and that was bad.. I see. then I will accept this pr

@Decodetalkers Decodetalkers merged commit 65e07a9 into waycrate:master May 22, 2026
4 checks passed
@krteke
Copy link
Copy Markdown
Contributor Author

krteke commented May 22, 2026

I see, you have already tried the upstream behavior, and that was bad.. I see. then I will accept this pr

Thanks for accepting it!

Emmm... One clarification: I don’t think this is an upstream iced issue. I tested normal iced with background_color: Color::TRANSPARENT, and the IME preedit text stays readable.

This looks specific to iced_layershell with a transparent layer-shell surface, where the selected preedit text can become transparent.

So I think fixing it in iced_layershell is the right approach, rather than changing iced upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants